From c010fc50bb3dbd2ef3046ab1f8f452e482089950 Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Fri, 4 Sep 2026 10:01:32 -0500 Subject: [PATCH 1/4] skip python/ruby encoding line if present in first two lines Signed-off-by: Gregory Becker --- sbang | 32 ++++++++++++++++++- test/shebangs/python_with_encoding_emacs.py | 5 +++ test/shebangs/python_with_encoding_minimal.py | 5 +++ test/shebangs/python_with_encoding_vim.py | 5 +++ test/shebangs/ruby_with_encoding.rb | 5 +++ test/test-suite.sh | 4 +++ 6 files changed, 55 insertions(+), 1 deletion(-) create mode 100755 test/shebangs/python_with_encoding_emacs.py create mode 100755 test/shebangs/python_with_encoding_minimal.py create mode 100755 test/shebangs/python_with_encoding_vim.py create mode 100755 test/shebangs/ruby_with_encoding.rb diff --git a/sbang b/sbang index 6478347..40c4fc2 100755 --- a/sbang +++ b/sbang @@ -22,6 +22,14 @@ # # See https://github.com/spack/sbang for more details. # +# If using python source code encoding, put that on the second line of the file +# and the shebang line for the long path to the real interpreter on the third +# line, like this: +# +# #!/bin/sh /path/to/sbang +# # -*- coding: utf-8 -*- +# #!/long/path/to/real/interpreter with arguments +# # Generic error handling die() { @@ -46,6 +54,7 @@ elif [ ! -f "$script" ]; then fi # Search the first two lines of script for interpreters. +# Search the third line if one of the first two is an encoding declaration lines=0 while read -r line && [ $lines -ne 2 ]; do if [ "${line#\#!}" != "$line" ]; then @@ -57,8 +66,29 @@ while read -r line && [ $lines -ne 2 ]; do elif [ "${line#}" + else + # Check whether this is a python/ruby encoding line + # Strip leading whitespace, whitespace is allowed before encoding + leading_whitespace=${line%%[![:space:]]*} + line=${line#"$leading_whitespace"} + # python/ruby encoding lines must start with comment character + if [ "${line#\#}" != "$line" ]; then + # If coding: or coding= in the line, it matches pep263 + case "$line" in + *"coding:"* | *"coding="*) + sbang_skip_encoding_line=1 + ;; + *) + ;; + esac + fi + fi + # Don't count the line as part of the two to search if it's an encoding + if [ "${sbang_skip_encoding_line}" = "1" ]; then + unset sbang_skip_encoding_line + else + lines=$((lines+1)) fi - lines=$((lines+1)) done < "$script" # error if we did not find any interpreter diff --git a/test/shebangs/python_with_encoding_emacs.py b/test/shebangs/python_with_encoding_emacs.py new file mode 100755 index 0000000..1171ed9 --- /dev/null +++ b/test/shebangs/python_with_encoding_emacs.py @@ -0,0 +1,5 @@ +#!/usr/bin/env sbang +# -*- coding: utf-8 -*- +#!/usr/bin/python + +print("python") diff --git a/test/shebangs/python_with_encoding_minimal.py b/test/shebangs/python_with_encoding_minimal.py new file mode 100755 index 0000000..9d6fbe4 --- /dev/null +++ b/test/shebangs/python_with_encoding_minimal.py @@ -0,0 +1,5 @@ +#!/usr/bin/env sbang +# coding: utf-8 +#!/usr/bin/python + +print("python") diff --git a/test/shebangs/python_with_encoding_vim.py b/test/shebangs/python_with_encoding_vim.py new file mode 100755 index 0000000..95ef94d --- /dev/null +++ b/test/shebangs/python_with_encoding_vim.py @@ -0,0 +1,5 @@ +#!/usr/bin/env sbang +# vim: set fileencoding=utf-8 +#!/usr/bin/python + +print("python") diff --git a/test/shebangs/ruby_with_encoding.rb b/test/shebangs/ruby_with_encoding.rb new file mode 100755 index 0000000..2943cd4 --- /dev/null +++ b/test/shebangs/ruby_with_encoding.rb @@ -0,0 +1,5 @@ +#!/usr/bin/env sbang +# encoding: utf-8 +#!/usr/bin/ruby + +puts "ruby" diff --git a/test/test-suite.sh b/test/test-suite.sh index aee584c..fe6ca05 100755 --- a/test/test-suite.sh +++ b/test/test-suite.sh @@ -33,12 +33,16 @@ equals "/usr/bin/env perl -w -x shebangs/perl-w-env.pl" $SBANG shebangs/perl-w-e equals "/usr/bin/ruby -x shebangs/ruby.rb" $SBANG shebangs/ruby.rb equals "/usr/bin/env ruby -x shebangs/ruby-env.rb" $SBANG shebangs/ruby-env.rb +equals "/usr/bin/ruby -x shebangs/ruby_with_encoding.rb" $SBANG shebangs/ruby_with_encoding.rb equals "/usr/bin/perl5.32.0 -x shebangs/perl-ver.pl" $SBANG shebangs/perl-ver.pl equals "/usr/bin/ruby2.7 -x shebangs/ruby-ver.rb" $SBANG shebangs/ruby-ver.rb equals "/usr/bin/python shebangs/python.py" $SBANG shebangs/python.py equals "/usr/bin/env python shebangs/python-env.py" $SBANG shebangs/python-env.py +equals "/usr/bin/python shebangs/python_with_encoding_emacs.py" $SBANG shebangs/python_with_encoding_emacs.py +equals "/usr/bin/python shebangs/python_with_encoding_minimal.py" $SBANG shebangs/python_with_encoding_minimal.py +equals "/usr/bin/python shebangs/python_with_encoding_vim.py" $SBANG shebangs/python_with_encoding_vim.py equals "/bin/sh shebangs/sh.sh" $SBANG shebangs/sh.sh equals "/bin/bash shebangs/bash.bash" $SBANG shebangs/bash.bash From a31596f5e8a00327878d0c40ad32d093e3d6909b Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Fri, 4 Sep 2026 10:17:58 -0500 Subject: [PATCH 2/4] update documentation Signed-off-by: Gregory Becker --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index ea27a3a..6b6ccee 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,19 @@ print "success!" ``` +### File encodings + +Python and Ruby support comments to control the file encoding only within +the first two lines of the file. When using sbang with encoding comments, +put the shebang line for sbang first, then the encoding comment, and then +the interpreter to use. E.g.: + +```sh +#!/bin/sh /path/to/sbang +# -*- coding: latin-1 -*- +#!/very/long/path/to/python +``` + ## How it works `sbang` is a very simple POSIX shell script. It looks at the first two From 32a3fd2c87e65bd91f26e98cf9a783825e1db28f Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Fri, 4 Sep 2026 16:19:28 -0500 Subject: [PATCH 3/4] slight refactor after review Signed-off-by: Gregory Becker --- sbang | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/sbang b/sbang index 40c4fc2..6b057c8 100755 --- a/sbang +++ b/sbang @@ -22,9 +22,9 @@ # # See https://github.com/spack/sbang for more details. # -# If using python source code encoding, put that on the second line of the file -# and the shebang line for the long path to the real interpreter on the third -# line, like this: +# If using python- or ruby- style source code encoding, put that on the second +# line of the file and the shebang line for the long path to the real +# interpreter on the third line, like this: # # #!/bin/sh /path/to/sbang # # -*- coding: utf-8 -*- @@ -59,36 +59,34 @@ lines=0 while read -r line && [ $lines -ne 2 ]; do if [ "${line#\#!}" != "$line" ]; then shebang_line="${line#\#!}" + lines=$((lines+1)) elif [ "${line#//!}" != "$line" ]; then # // comments shebang_line="${line#//!}" + lines=$((lines+1)) elif [ "${line#--!}" != "$line" ]; then # -- lua comments shebang_line="${line#--!}" + lines=$((lines+1)) elif [ "${line#}" + lines=$((lines+1)) else # Check whether this is a python/ruby encoding line # Strip leading whitespace, whitespace is allowed before encoding leading_whitespace=${line%%[![:space:]]*} line=${line#"$leading_whitespace"} - # python/ruby encoding lines must start with comment character + + # encoding declaration starts with comment character if [ "${line#\#}" != "$line" ]; then - # If coding: or coding= in the line, it matches pep263 case "$line" in *"coding:"* | *"coding="*) - sbang_skip_encoding_line=1 - ;; + ;; # skip encoding line *) + lines=$((lines+1)) ;; esac fi fi - # Don't count the line as part of the two to search if it's an encoding - if [ "${sbang_skip_encoding_line}" = "1" ]; then - unset sbang_skip_encoding_line - else - lines=$((lines+1)) - fi done < "$script" # error if we did not find any interpreter From e8cb7f124562a7a18a6eac903a7296cc33eeb58b Mon Sep 17 00:00:00 2001 From: Gregory Becker Date: Fri, 4 Sep 2026 16:28:38 -0500 Subject: [PATCH 4/4] add test for 'encoding=' in minimal format Signed-off-by: Gregory Becker --- test/shebangs/python_with_encoding_equals.py | 5 +++++ test/test-suite.sh | 1 + 2 files changed, 6 insertions(+) create mode 100755 test/shebangs/python_with_encoding_equals.py diff --git a/test/shebangs/python_with_encoding_equals.py b/test/shebangs/python_with_encoding_equals.py new file mode 100755 index 0000000..41ac909 --- /dev/null +++ b/test/shebangs/python_with_encoding_equals.py @@ -0,0 +1,5 @@ +#!/usr/bin/env sbang +# encoding=utf-8 +#!/usr/bin/python + +print("python") diff --git a/test/test-suite.sh b/test/test-suite.sh index fe6ca05..4e40740 100755 --- a/test/test-suite.sh +++ b/test/test-suite.sh @@ -42,6 +42,7 @@ equals "/usr/bin/python shebangs/python.py" $SBANG shebangs/python.p equals "/usr/bin/env python shebangs/python-env.py" $SBANG shebangs/python-env.py equals "/usr/bin/python shebangs/python_with_encoding_emacs.py" $SBANG shebangs/python_with_encoding_emacs.py equals "/usr/bin/python shebangs/python_with_encoding_minimal.py" $SBANG shebangs/python_with_encoding_minimal.py +equals "/usr/bin/python shebangs/python_with_encoding_equals.py" $SBANG shebangs/python_with_encoding_equals.py equals "/usr/bin/python shebangs/python_with_encoding_vim.py" $SBANG shebangs/python_with_encoding_vim.py equals "/bin/sh shebangs/sh.sh" $SBANG shebangs/sh.sh