├── .codecov.yml ├── .github └── workflows │ ├── linux-test.yaml │ ├── macos-tests.yaml │ └── shellcheck.yaml ├── .gitignore ├── CHANGELOG.md ├── COPYRIGHT ├── LICENSE-APACHE ├── LICENSE-MIT ├── NOTICE ├── README.md ├── sbang └── test ├── Makefile ├── shebangs ├── arg1.sh ├── arg2.sh ├── arg3.sh ├── arg4.sh ├── bash.bash ├── lua.lua ├── no-interpreter ├── node.js ├── perl-env.pl ├── perl-ver.pl ├── perl-w-env.pl ├── perl-w.pl ├── perl.pl ├── php.php ├── python-env.py ├── python.py ├── ruby-env.rb ├── ruby-ver.rb ├── ruby.rb ├── sbang ├── sbang-env └── sh.sh ├── test-framework.sh └── test-suite.sh /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: nearest 4 | range: 60...90 5 | status: 6 | project: 7 | default: 8 | threshold: 0.2% 9 | 10 | ignore: 11 | - test/.* 12 | 13 | comment: off 14 | -------------------------------------------------------------------------------- /.github/workflows/linux-test.yaml: -------------------------------------------------------------------------------- 1 | name: linux 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | jobs: 11 | unit-tests: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: run unit tests 18 | run: | 19 | make -C test test 20 | -------------------------------------------------------------------------------- /.github/workflows/macos-tests.yaml: -------------------------------------------------------------------------------- 1 | name: macos 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | jobs: 11 | unit-tests: 12 | runs-on: macos-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Setup Homebrew packages 18 | run: | 19 | brew install kcov 20 | - name: Run unit tests 21 | env: 22 | COVERAGE: true 23 | run: make -C test test 24 | - uses: codecov/codecov-action@v1 25 | with: 26 | directory: ./coverage 27 | flags: unittests 28 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yaml: -------------------------------------------------------------------------------- 1 | name: shellcheck 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | jobs: 11 | shellcheck: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: install shellcheck 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install shellcheck 21 | - name: run unit tests 22 | run: make -C test shellcheck 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.0 (2020-11-18) 2 | 3 | This is the first standalone release of `sbang` 4 | 5 | * Completely POSIX-compatible `sbang` implementation 6 | * Support for regular `#` shebangs, `lua`, `node`, and `php` 7 | * Special -x handling for `perl` and `ruby` 8 | * Unit tests with full coverage 9 | * Standalone release number, separate from Spack 10 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Intellectual Property Notice 2 | ------------------------------ 3 | 4 | sbang is licensed under the Apache License, Version 2.0 (LICENSE-APACHE 5 | or http://www.apache.org/licenses/LICENSE-2.0) or the MIT license, 6 | (LICENSE-MIT or http://opensource.org/licenses/MIT), at your option. 7 | 8 | Copyrights and patents in the sbang project are retained by contributors. 9 | No copyright assignment is required to contribute to sbang. 10 | 11 | 12 | SPDX usage 13 | ------------ 14 | 15 | Individual files contain SPDX tags instead of the full license text. 16 | This enables machine processing of license information based on the SPDX 17 | License Identifiers that are available here: https://spdx.org/licenses/ 18 | 19 | Files that are dual-licensed as Apache-2.0 OR MIT contain the following 20 | text in the license header: 21 | 22 | SPDX-License-Identifier: (Apache-2.0 OR MIT) 23 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-2020 LLNS, LLC and other sbang Project Developers. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This work was produced under the auspices of the U.S. Department of 2 | Energy by Lawrence Livermore National Laboratory under Contract 3 | DE-AC52-07NA27344. 4 | 5 | This work was prepared as an account of work sponsored by an agency of 6 | the United States Government. Neither the United States Government nor 7 | Lawrence Livermore National Security, LLC, nor any of their employees 8 | makes any warranty, expressed or implied, or assumes any legal liability 9 | or responsibility for the accuracy, completeness, or usefulness of any 10 | information, apparatus, product, or process disclosed, or represents that 11 | its use would not infringe privately owned rights. 12 | 13 | Reference herein to any specific commercial product, process, or service 14 | by trade name, trademark, manufacturer, or otherwise does not necessarily 15 | constitute or imply its endorsement, recommendation, or favoring by the 16 | United States Government or Lawrence Livermore National Security, LLC. 17 | 18 | The views and opinions of authors expressed herein do not necessarily 19 | state or reflect those of the United States Government or Lawrence 20 | Livermore National Security, LLC, and shall not be used for advertising 21 | or product endorsement purposes. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sbang 2 | ![linux](https://github.com/spack/sbang/workflows/linux/badge.svg) 3 | ![macos](https://github.com/spack/sbang/workflows/macos/badge.svg) 4 | ![shellcheck](https://github.com/spack/sbang/workflows/shellcheck/badge.svg) 5 | [![codecov](https://codecov.io/gh/spack/sbang/branch/main/graph/badge.svg?token=IKH7mB5qq7)](https://codecov.io/gh/spack/sbang) 6 | 7 | `sbang` lets you run scripts with very long shebang (`#!`) lines. 8 | 9 | Many operating systems limit the length and number of possible arguments 10 | in shebang lines, making it hard to use interpreters that are deep in the 11 | directory hierarchy or require special arguments. 12 | 13 | To use, put the long shebang on the second line of your script, and 14 | make sbang the interpreter, like this: 15 | 16 | ```sh 17 | #!/bin/sh /path/to/sbang 18 | #!/long/path/to/real/interpreter with many arguments 19 | ``` 20 | 21 | `sbang` will run the real interpreter with the script as its argument. 22 | 23 | ## Why? 24 | 25 | Most people don't have long shebang problems. They can come up if you 26 | install software in deeply nested directories. e.g., in your home 27 | directory (with something like [Spack](https://github.com/spack/spack)), 28 | or in a shared project directory on an NFS volume. It also comes up in 29 | deeply nested [virtual environments](https://github.com/pypa/virtualenv), 30 | where the `python` interpreter is copied into a deep path. 31 | 32 | Generally, `sbang` is useful for user-installed code. Admins who have 33 | root and can put software wherever they want will likely not need it. 34 | 35 | ### Long shebangs 36 | 37 | Suppose you have a script, `long-shebang.sh`, like this: 38 | 39 | ```sh 40 | #!/very/very/long/path/to/some/interp 41 | 42 | echo "success!" 43 | ``` 44 | 45 | If `very/long/path` is actually very long, running this script will 46 | result in an error on some OS's. On Linux, you get an error this: 47 | 48 | ```console 49 | $ ./long-shebang.sh 50 | -bash: ./long=shebang.sh: /very/very/long/path/to/some/interp: bad interpreter: 51 | No such file or directory 52 | ``` 53 | 54 | On macOS, things are worse. The system doesn't consider the long 55 | interpreter path, and just tries to run the script with the shell. This 56 | is not likely to be what you intended. 57 | 58 | ### Shebangs with arguments 59 | 60 | Passing arguments on the shebang line is an issue. Consider: 61 | 62 | ```sh 63 | #!/path/to/interp -a -b -c 64 | 65 | ... 66 | ``` 67 | 68 | Depending on your OS, `interp` may end up receiving a single argument 69 | like `"-a -b -c"` instead of three separate arguments (`"-a"`, `"-b"`, 70 | `"-c"`). `sbang` will delegate shebang arguments separately, as you would 71 | expect, so you can do this: 72 | 73 | ```sh 74 | #!/bin/sh /path/to/sbang 75 | #!/path/to/interp -a -b -c 76 | 77 | ... 78 | ``` 79 | 80 | ### Further reading 81 | 82 | There's a really comprehensive writeup on the history and limitations of 83 | the shebang mechanism at 84 | https://www.in-ulm.de/~mascheck/various/shebang/. 85 | 86 | 87 | ## Using `sbang` 88 | 89 | You can use `sbang` in several ways. 90 | 91 | ### `sbang` on the command line 92 | 93 | You can use `sbang` in two ways. You can use it directly, from the 94 | command line, like this: 95 | 96 | ```console 97 | $ sbang ./long-shebang.sh 98 | success! 99 | ``` 100 | 101 | ### `sbang` as the interpreter 102 | 103 | You can also use `sbang` *as* the interpreter for your script. Put 104 | `#!/bin/sh /path/to/sbang` on line 1, and move the original shebang to 105 | line 2 of the script: 106 | 107 | ```sh 108 | #!/bin/sh /path/to/sbang 109 | #!/long/path/to/real/interpreter with arguments 110 | 111 | echo "success!" 112 | ``` 113 | 114 | ```console 115 | $ ./long-shebang.sh 116 | success! 117 | ``` 118 | 119 | On Linux, you could shorten line 1 to `#!/path/to/sbang`, but other 120 | operating systems like Mac OS X require the interpreter to be a binary, 121 | so it's best to use `sbang` as an argument to `/bin/sh`. Obviously, for 122 | this to work, `sbang` needs to have a short enough path that *it* will 123 | run without hitting OS limits. 124 | 125 | ### Other comment syntaxes 126 | 127 | For Lua, node, and php scripts, the second line can't start with `#!`, as 128 | `#` is not the comment character in these languages (though they all 129 | ignore `#!` on the *first* line of a script). Instrument such scripts 130 | like this, using `--`, `//`, or `` instead of `#` on the 131 | second line, e.g.: 132 | 133 | ```sh 134 | #!/bin/sh /path/to/sbang 135 | --!/long/path/to/lua with arguments 136 | print "success!" 137 | ``` 138 | 139 | ```sh 140 | #!/bin/sh /path/to/sbang 141 | //!/long/path/to/node with arguments 142 | print "success!" 143 | ``` 144 | 145 | ```sh 146 | #!/bin/sh /path/to/sbang 147 | 148 | 149 | ``` 150 | 151 | ## How it works 152 | 153 | `sbang` is a very simple POSIX shell script. It looks at the first two 154 | lines of a script argument and runs the last line starting with `#!`, 155 | with the script as an argument. It also forwards arguments. Because it's 156 | simple POSIX, you can use it almost anywhere. 157 | 158 | 159 | ## Authors 160 | 161 | `sbang` was created by Todd Gamblin, tgamblin@llnl.gov, as part of 162 | [Spack](https://github.com/spack/spack). 163 | 164 | ## Related projects 165 | 166 | The [long-shebang](https://github.com/shlevy/long-shebang) project is 167 | like `sbang` but written in C instead of POSIX `sh`. It grew out of 168 | [Nix](https://github.com/nixos/nix) a few months after `sbang` grew out 169 | of Spack, for similar reasons. 170 | 171 | ## License 172 | 173 | `sbang` is distributed under the terms of both the MIT license and the 174 | Apache License (Version 2.0). Users may choose either license, at their 175 | option. 176 | 177 | All new contributions must be made under both the MIT and Apache-2.0 178 | licenses. 179 | 180 | See [LICENSE-MIT](https://github.com/spack/sbang/blob/develop/LICENSE-MIT), 181 | [LICENSE-APACHE](https://github.com/spack/sbang/blob/develop/LICENSE-APACHE), 182 | [COPYRIGHT](https://github.com/spack/sbang/blob/develop/COPYRIGHT), and 183 | [NOTICE](https://github.com/spack/sbang/blob/develop/NOTICE) for details. 184 | 185 | SPDX-License-Identifier: (Apache-2.0 OR MIT) 186 | 187 | LLNL-CODE-816912 188 | -------------------------------------------------------------------------------- /sbang: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2013-2020 Lawrence Livermore National Security, LLC and other 4 | # sbang project developers. See the top-level COPYRIGHT file for details. 5 | # 6 | # SPDX-License-Identifier: (Apache-2.0 OR MIT) 7 | 8 | # 9 | # `sbang`: Run scripts with long shebang lines. 10 | # 11 | # Many operating systems limit the length and number of possible 12 | # arguments in shebang lines, making it hard to use interpreters that are 13 | # deep in the directory hierarchy or require special arguments. 14 | # 15 | # To use, put the long shebang on the second line of your script, and 16 | # make sbang the interpreter, like this: 17 | # 18 | # #!/bin/sh /path/to/sbang 19 | # #!/long/path/to/real/interpreter with arguments 20 | # 21 | # `sbang` will run the real interpreter with the script as its argument. 22 | # 23 | # See https://github.com/spack/sbang for more details. 24 | # 25 | 26 | # Generic error handling 27 | die() { 28 | echo "$@" 1>&2; 29 | exit 1 30 | } 31 | 32 | # set SBANG_DEBUG to make the script print what would normally be executed. 33 | exec="exec" 34 | if [ -n "${SBANG_DEBUG}" ]; then 35 | exec="echo " 36 | fi 37 | 38 | # First argument is the script we want to actually run. 39 | script="$1" 40 | 41 | # ensure that the script actually exists 42 | if [ -z "$script" ]; then 43 | die "error: sbang requires exactly one argument" 44 | elif [ ! -f "$script" ]; then 45 | die "$script: no such file or directory" 46 | fi 47 | 48 | # Search the first two lines of script for interpreters. 49 | lines=0 50 | while read -r line && [ $lines -ne 2 ]; do 51 | if [ "${line#\#!}" != "$line" ]; then 52 | shebang_line="${line#\#!}" 53 | elif [ "${line#//!}" != "$line" ]; then # // comments 54 | shebang_line="${line#//!}" 55 | elif [ "${line#--!}" != "$line" ]; then # -- lua comments 56 | shebang_line="${line#--!}" 57 | elif [ "${line#}" 60 | fi 61 | lines=$((lines+1)) 62 | done < "$script" 63 | 64 | # error if we did not find any interpreter 65 | if [ -z "$shebang_line" ]; then 66 | die "error: sbang found no interpreter in $script" 67 | fi 68 | 69 | # parse out the interpreter and first argument 70 | IFS=' ' read -r interpreter arg1 rest < 3 | 4 | -------------------------------------------------------------------------------- /test/shebangs/python-env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/usr/bin/env python 3 | 4 | from __future__ import print_function 5 | 6 | print("python") 7 | -------------------------------------------------------------------------------- /test/shebangs/python.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/usr/bin/python 3 | 4 | from __future__ import print_function 5 | 6 | print("python") 7 | -------------------------------------------------------------------------------- /test/shebangs/ruby-env.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/usr/bin/env ruby 3 | 4 | puts "ruby" 5 | -------------------------------------------------------------------------------- /test/shebangs/ruby-ver.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/usr/bin/ruby2.7 3 | 4 | puts "ruby" 5 | -------------------------------------------------------------------------------- /test/shebangs/ruby.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/usr/bin/ruby 3 | 4 | puts "ruby" 5 | -------------------------------------------------------------------------------- /test/shebangs/sbang: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/path/to/sbang 3 | -------------------------------------------------------------------------------- /test/shebangs/sbang-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/usr/bin/env sbang 3 | -------------------------------------------------------------------------------- /test/shebangs/sh.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sbang 2 | #!/bin/sh 3 | -------------------------------------------------------------------------------- /test/test-framework.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2013-2020 Lawrence Livermore National Security, LLC and other 2 | # sbang project developers. See the top-level COPYRIGHT file for details. 3 | # 4 | # SPDX-License-Identifier: (Apache-2.0 OR MIT) 5 | 6 | # 7 | # A testing framework for any POSIX-compatible shell. 8 | # 9 | 10 | # ------------------------------------------------------------------------ 11 | # Functions for color output. 12 | # ------------------------------------------------------------------------ 13 | 14 | # Colors for output 15 | red='\033[1;31m' 16 | cyan='\033[1;36m' 17 | green='\033[1;32m' 18 | reset='\033[0m' 19 | 20 | echo_red() { 21 | printf "${red}$*${reset}\n" 22 | } 23 | 24 | echo_green() { 25 | printf "${green}$*${reset}\n" 26 | } 27 | 28 | echo_msg() { 29 | printf "${cyan}$*${reset}\n" 30 | } 31 | 32 | # ------------------------------------------------------------------------ 33 | # Generic functions for testing shell code. 34 | # ------------------------------------------------------------------------ 35 | 36 | # counts of test successes and failures. 37 | success=0 38 | errors=0 39 | 40 | # Print out a header for a group of tests. 41 | title() { 42 | echo 43 | echo_msg "$@" 44 | echo_msg "---------------------------------" 45 | } 46 | 47 | # echo FAIL in red text; increment failures 48 | fail() { 49 | echo_red FAIL 50 | errors=$((errors+1)) 51 | } 52 | 53 | # 54 | # Echo SUCCESS in green; increment successes 55 | # 56 | pass() { 57 | echo_green SUCCESS 58 | success=$((success+1)) 59 | } 60 | 61 | # 62 | # Run a command and suppress output unless it fails. 63 | # On failure, echo the exit code and output. 64 | # 65 | succeeds() { 66 | printf "'%s' succeeds ... " "$*" 67 | output=$("$@" 2>&1) 68 | err="$?" 69 | 70 | if [ "$err" != 0 ]; then 71 | fail 72 | echo_red "Command failed with error $err." 73 | if [ -n "$output" ]; then 74 | echo_msg "Output:" 75 | echo "$output" 76 | else 77 | echo_msg "No output." 78 | fi 79 | else 80 | pass 81 | fi 82 | } 83 | 84 | # 85 | # Run a command and suppress output unless it succeeds. 86 | # If the command succeeds, echo the output. 87 | # 88 | fails() { 89 | printf "'%s' fails ... " "$*" 90 | output=$("$@" 2>&1) 91 | err="$?" 92 | 93 | if [ "$err" = 0 ]; then 94 | fail 95 | echo_red "Command failed with error $err." 96 | if [ -n "$output" ]; then 97 | echo_msg "Output:" 98 | echo "$output" 99 | else 100 | echo_msg "No output." 101 | fi 102 | else 103 | pass 104 | fi 105 | } 106 | 107 | # 108 | # Ensure that a string is in the output of a command. 109 | # Suppresses output on success. 110 | # On failure, echo the exit code and output. 111 | # 112 | contains() { 113 | string="$1" 114 | shift 115 | 116 | printf "'%s' output contains '$string' ... " "$*" 117 | output=$("$@" 2>&1) 118 | err="$?" 119 | 120 | if [ "${output#*$string}" = "${output}" ]; then 121 | fail 122 | echo_red "Command exited with error $err." 123 | echo_red "'$string' was not in output." 124 | if [ -n "$output" ]; then 125 | echo_msg "Output:" 126 | echo "$output" 127 | else 128 | echo_msg "No output." 129 | fi 130 | else 131 | pass 132 | fi 133 | } 134 | 135 | # 136 | # Ensure that a string is the exact output of a command. 137 | # Suppresses output on success. 138 | # On failure, echo the exit code and output. 139 | # 140 | equals() { 141 | string="$1" 142 | shift 143 | 144 | printf "'%s' output is '$string' ... " "$*" 145 | output=$("$@" 2>&1) 146 | err="$?" 147 | 148 | if [ "${output#*$string}" = "" ]; then 149 | pass 150 | else 151 | fail 152 | echo_red "Command exited with error $err." 153 | echo_red "output != '$string'." 154 | if [ -n "$output" ]; then 155 | echo_msg "Output:" 156 | echo "$output" 157 | else 158 | echo_msg "No output." 159 | fi 160 | fi 161 | } 162 | 163 | # 164 | # Ensure that a variable is set. 165 | # 166 | is_set() { 167 | printf "'%s' is set ... " "$1" 168 | if eval "[ -z \${${1:-}+x} ]"; then 169 | fail 170 | echo_msg "$1 was not set!" 171 | else 172 | pass 173 | fi 174 | } 175 | 176 | # 177 | # Ensure that a variable is not set. 178 | # Fails and prints the value of the variable if it is set. 179 | # 180 | is_not_set() { 181 | printf "'%s' is not set ... " "$1" 182 | if eval "[ ! -z \${${1:-}+x} ]"; then 183 | fail 184 | echo_msg "$1 was set:" 185 | echo " $1" 186 | else 187 | pass 188 | fi 189 | } 190 | 191 | # 192 | # Report the number of tests that succeeded and failed on exit. 193 | # 194 | teardown() { 195 | if [ "$?" != 0 ]; then 196 | trapped_error=true 197 | else 198 | trapped_error=false 199 | fi 200 | 201 | if type cleanup &> /dev/null 202 | then 203 | cleanup 204 | fi 205 | 206 | echo 207 | echo "$success tests succeeded." 208 | echo "$errors tests failed." 209 | 210 | if [ "$trapped_error" = true ]; then 211 | echo "Exited due to an error." 212 | fi 213 | 214 | if [ "$errors" = 0 ] && [ "$trapped_error" = false ]; then 215 | pass 216 | exit 0 217 | else 218 | fail 219 | exit 1 220 | fi 221 | } 222 | 223 | trap teardown EXIT 224 | -------------------------------------------------------------------------------- /test/test-suite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2013-2020 Lawrence Livermore National Security, LLC and other 4 | # sbang project developers. See the top-level COPYRIGHT file for details. 5 | # 6 | # SPDX-License-Identifier: (Apache-2.0 OR MIT) 7 | 8 | set -u 9 | . ./test-framework.sh 10 | 11 | # you must set SBANG when running the test suite 12 | if [ -z "${SBANG:-}" ]; then 13 | echo_red "error: must set SBANG to location of sbang script" 14 | exit 1 15 | fi 16 | 17 | # ensure that arguments are not all passed together as one big string, 18 | # and that escaped arguments are handled. 19 | title "Test arguments are parsed and passed properly" 20 | equals "arg1" $SBANG shebangs/arg1.sh arg1 "arg2 arg2" arg3\ arg3 arg4 21 | equals "arg2 arg2" $SBANG shebangs/arg2.sh arg1 "arg2 arg2" arg3\ arg3 arg4 22 | equals "arg3 arg3" $SBANG shebangs/arg3.sh arg1 "arg2 arg2" arg3\ arg3 arg4 23 | equals "arg4" $SBANG shebangs/arg4.sh arg1 "arg2 arg2" arg3\ arg3 arg4 24 | 25 | # enable debug mode for tests below to work -- this outputs what would be run 26 | export SBANG_DEBUG=1 27 | 28 | title "Testing languages with shell-style comments" 29 | equals "/usr/bin/perl -x shebangs/perl.pl" $SBANG shebangs/perl.pl 30 | equals "/usr/bin/env perl -x shebangs/perl-env.pl" $SBANG shebangs/perl-env.pl 31 | equals "/usr/bin/perl -w -x shebangs/perl-w.pl" $SBANG shebangs/perl-w.pl 32 | equals "/usr/bin/env perl -w -x shebangs/perl-w-env.pl" $SBANG shebangs/perl-w-env.pl 33 | 34 | equals "/usr/bin/ruby -x shebangs/ruby.rb" $SBANG shebangs/ruby.rb 35 | equals "/usr/bin/env ruby -x shebangs/ruby-env.rb" $SBANG shebangs/ruby-env.rb 36 | 37 | equals "/usr/bin/perl5.32.0 -x shebangs/perl-ver.pl" $SBANG shebangs/perl-ver.pl 38 | equals "/usr/bin/ruby2.7 -x shebangs/ruby-ver.rb" $SBANG shebangs/ruby-ver.rb 39 | 40 | equals "/usr/bin/python shebangs/python.py" $SBANG shebangs/python.py 41 | equals "/usr/bin/env python shebangs/python-env.py" $SBANG shebangs/python-env.py 42 | 43 | equals "/bin/sh shebangs/sh.sh" $SBANG shebangs/sh.sh 44 | equals "/bin/bash shebangs/bash.bash" $SBANG shebangs/bash.bash 45 | 46 | title "Testing languages without shell-style comments" 47 | equals "/path/to/lua shebangs/lua.lua" $SBANG shebangs/lua.lua 48 | equals "/path/to/node shebangs/node.js" $SBANG shebangs/node.js 49 | equals "/usr/bin/php shebangs/php.php" $SBANG shebangs/php.php 50 | 51 | title "Testing sbang fails with invalid input" 52 | fails $SBANG 53 | fails $SBANG nonexistent-file 54 | fails $SBANG shebangs/no-interpreter 55 | 56 | title "Testing sbang doesn't loop infinitely" 57 | fails $SBANG shebangs/sbang 58 | fails $SBANG shebangs/sbang-env 59 | --------------------------------------------------------------------------------