Merge pull request #8470 from ncfavier/shebang-single-quotes

nix-shell: support single quotes in shebangs, fix whitespace parsing
(cherry picked from commit 3b99c6291377cbd22607896af9dfafa857d2f2dc)
Change-Id: I2a431b21c3467eefa1ef95d5a36d672f45b6937a
This commit is contained in:
eldritch horrors 2024-03-04 05:06:32 +01:00
parent 7f590ea709
commit 032eff7f69
5 changed files with 46 additions and 15 deletions

View file

@ -235,14 +235,14 @@ package like Terraform:
```bash ```bash
#! /usr/bin/env nix-shell #! /usr/bin/env nix-shell
#! nix-shell -i bash --packages "terraform.withPlugins (plugins: [ plugins.openstack ])" #! nix-shell -i bash --packages 'terraform.withPlugins (plugins: [ plugins.openstack ])'
terraform apply terraform apply
``` ```
> **Note** > **Note**
> >
> You must use double quotes (`"`) when passing a simple Nix expression > You must use single or double quotes (`'`, `"`) when passing a simple Nix expression
> in a nix-shell shebang. > in a nix-shell shebang.
Finally, using the merging of multiple nix-shell shebangs the following Finally, using the merging of multiple nix-shell shebangs the following
@ -251,7 +251,7 @@ branch):
```haskell ```haskell
#! /usr/bin/env nix-shell #! /usr/bin/env nix-shell
#! nix-shell -i runghc --packages "haskellPackages.ghcWithPackages (ps: [ps.download-curl ps.tagsoup])" #! nix-shell -i runghc --packages 'haskellPackages.ghcWithPackages (ps: [ps.download-curl ps.tagsoup])'
#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-20.03.tar.gz #! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-20.03.tar.gz
import Network.Curl.Download import Network.Curl.Download

View file

@ -1 +1,3 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
- `nix-shell` shebang lines now support single-quoted arguments.

View file

@ -34,13 +34,14 @@ extern char * * environ __attribute__((weak));
*/ */
static std::vector<std::string> shellwords(const std::string & s) static std::vector<std::string> shellwords(const std::string & s)
{ {
std::regex whitespace("^(\\s+).*"); std::regex whitespace("^\\s+");
auto begin = s.cbegin(); auto begin = s.cbegin();
std::vector<std::string> res; std::vector<std::string> res;
std::string cur; std::string cur;
enum state { enum state {
sBegin, sBegin,
sQuote sSingleQuote,
sDoubleQuote
}; };
state st = sBegin; state st = sBegin;
auto it = begin; auto it = begin;
@ -50,26 +51,39 @@ static std::vector<std::string> shellwords(const std::string & s)
if (regex_search(it, s.cend(), match, whitespace)) { if (regex_search(it, s.cend(), match, whitespace)) {
cur.append(begin, it); cur.append(begin, it);
res.push_back(cur); res.push_back(cur);
cur.clear(); it = match[0].second;
it = match[1].second; if (it == s.cend()) return res;
begin = it; begin = it;
cur.clear();
} }
} }
switch (*it) { switch (*it) {
case '\'':
if (st != sDoubleQuote) {
cur.append(begin, it);
begin = it + 1;
st = st == sBegin ? sSingleQuote : sBegin;
}
break;
case '"': case '"':
cur.append(begin, it); if (st != sSingleQuote) {
begin = it + 1; cur.append(begin, it);
st = st == sBegin ? sQuote : sBegin; begin = it + 1;
st = st == sBegin ? sDoubleQuote : sBegin;
}
break; break;
case '\\': case '\\':
/* perl shellwords mostly just treats the next char as part of the string with no special processing */ if (st != sSingleQuote) {
cur.append(begin, it); /* perl shellwords mostly just treats the next char as part of the string with no special processing */
begin = ++it; cur.append(begin, it);
begin = ++it;
}
break; break;
} }
} }
if (st != sBegin) throw Error("unterminated quote in shebang line");
cur.append(begin, it); cur.append(begin, it);
if (!cur.empty()) res.push_back(cur); res.push_back(cur);
return res; return res;
} }
@ -128,7 +142,7 @@ static void main_nix_build(int argc, char * * argv)
for (auto line : lines) { for (auto line : lines) {
line = chomp(line); line = chomp(line);
std::smatch match; std::smatch match;
if (std::regex_match(line, match, std::regex("^#!\\s*nix-shell (.*)$"))) if (std::regex_match(line, match, std::regex("^#!\\s*nix-shell\\s+(.*)$")))
for (const auto & word : shellwords(match[1].str())) for (const auto & word : shellwords(match[1].str()))
args.push_back(word); args.push_back(word);
} }

View file

@ -84,6 +84,11 @@ chmod a+rx $TEST_ROOT/spaced\ \\\'\"shell.shebang.rb
output=$($TEST_ROOT/spaced\ \\\'\"shell.shebang.rb abc ruby) output=$($TEST_ROOT/spaced\ \\\'\"shell.shebang.rb abc ruby)
[ "$output" = '-e load(ARGV.shift) -- '"$TEST_ROOT"'/spaced \'\''"shell.shebang.rb abc ruby' ] [ "$output" = '-e load(ARGV.shift) -- '"$TEST_ROOT"'/spaced \'\''"shell.shebang.rb abc ruby' ]
# Test nix-shell shebang quoting
sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.nix > $TEST_ROOT/shell.shebang.nix
chmod a+rx $TEST_ROOT/shell.shebang.nix
$TEST_ROOT/shell.shebang.nix
# Test 'nix develop'. # Test 'nix develop'.
nix develop -f "$shellDotNix" shellDrv -c bash -c '[[ -n $stdenv ]]' nix develop -f "$shellDotNix" shellDrv -c bash -c '[[ -n $stdenv ]]'

View file

@ -0,0 +1,10 @@
#! @ENV_PROG@ nix-shell
#! nix-shell -I nixpkgs=shell.nix --no-substitute
#! nix-shell --argstr s1 'foo "bar" \baz'"'"'qux' --argstr s2 "foo 'bar' \"\baz" --argstr s3 \foo\ bar\'baz --argstr s4 ''
#! nix-shell shell.shebang.nix --command true
{ s1, s2, s3, s4 }:
assert s1 == ''foo "bar" \baz'qux'';
assert s2 == "foo 'bar' \"baz";
assert s3 == "foo bar'baz";
assert s4 == "";
(import <nixpkgs> {}).runCommand "nix-shell" {} ""