summaryrefslogtreecommitdiff
path: root/bin/sbang
blob: 472753481246373046d511e73d416093050da9b2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/bin/sh
#
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# sbang project developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

#
# `sbang`: Run scripts with long shebang lines.
#
# Many operating systems limit the length and number of possible
# arguments in shebang lines, making it hard to use interpreters that are
# deep in the directory hierarchy or require special arguments.
#
# To use, put the long shebang on the second line of your script, and
# make sbang the interpreter, like this:
#
#     #!/bin/sh /path/to/sbang
#     #!/long/path/to/real/interpreter with arguments
#
# `sbang` will run the real interpreter with the script as its argument.
#
# See https://github.com/spack/sbang for more details.
#

# Generic error handling
die() {
    echo "$@" 1>&2;
    exit 1
}

# set SBANG_DEBUG to make the script print what would normally be executed.
exec="exec"
if [ -n "${SBANG_DEBUG}" ]; then
    exec="echo "
fi

# First argument is the script we want to actually run.
script="$1"

# ensure that the script actually exists
if [ -z "$script" ]; then
    die "error: sbang requires exactly one argument"
elif [ ! -f "$script" ]; then
    die "$script: no such file or directory"
fi

# Search the first two lines of script for interpreters.
lines=0
while read -r line && [ $lines -ne 2 ]; do
    if [ "${line#\#!}" != "$line" ]; then
        shebang_line="${line#\#!}"
    elif [ "${line#//!}" != "$line" ]; then      # // comments
        shebang_line="${line#//!}"
    elif [ "${line#--!}" != "$line" ]; then      # -- lua comments
        shebang_line="${line#--!}"
    elif [ "${line#<?php\ }" != "$line" ]; then  # php comments
        shebang_line="${line#<?php\ \#!}"
        shebang_line="${shebang_line%\ ?>}"
    fi
    lines=$((lines+1))
done < "$script"

# error if we did not find any interpreter
if [ -z "$shebang_line"  ]; then
    die "error: sbang found no interpreter in $script"
fi

# parse out the interpreter and first argument
IFS=' ' read -r interpreter arg1 rest <<EOF
$shebang_line
EOF

# Determine if the interpreter is a particular program, accounting for the
# '#!/usr/bin/env PROGRAM' convention. So:
#
#     interpreter_is perl
#
# will be true for '#!/usr/bin/perl' and '#!/usr/bin/env perl'
interpreter_is() {
    if [ "${interpreter##*/}" = "$1" ]; then
        return 0
    elif [ "$interpreter" = "/usr/bin/env" ] && [ "$arg1" = "$1" ]; then
        return 0
    else
        return 1
    fi
}

if interpreter_is "sbang"; then
    die "error: refusing to re-execute sbang to avoid infinite loop."
fi

# Finally invoke the real shebang line
# ruby and perl need -x to ignore the first line of input (the sbang line)
#
if interpreter_is perl || interpreter_is ruby; then
    # shellcheck disable=SC2086
    $exec $shebang_line -x "$@"
else
    # shellcheck disable=SC2086
    $exec $shebang_line "$@"
fi