Shell-Script

如果我從“sudo –login”執行它,“sh -c”不會擴展位置參數。有沒有解決的辦法?

  • March 30, 2021

我寫了一個使用位置參數的“內聯腳本”,例如

$ sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2

我實際上想使用sudo --login. 但是位置參數不起作用。有沒有辦法讓它們工作?

(如果行為在某處被記錄或標準化,我也很感興趣)。

$ sudo --login sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=-bash
p1=
p2=
all=1 2

軟體版本:

$ sh --version
GNU bash, version 5.0.7(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ sudo --version
Sudo version 1.8.27
Sudoers policy plugin version 1.8.27
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.27

出於同樣的原因

sudo --login echo '$HOME'

不輸出$HOME,而是$HOME變數的內容(目標使用者的,由 設置sudo)。

在您的情況下,這不是因為sh -c 沒有擴展位置參數,而是因為$0, $1,在啟動$2時已經(通過登錄外殼)擴展了sh

這裡更令人驚訝的是為什麼你會看到all=1 2. $@這與您看到的原因相同sudo --login echo '$@'

-s/--shell-i/都--login執行一個 shell 來執行命令。但sudo它以一種非常奇怪的方式進行。

我希望sudo -s 'some shell code'執行一個 shell 來解釋some shell code,但這不是它的作用。使用-s,它仍然希望執行一個簡單的命令,並嘗試引用(使用\)一些字元,以希望通過該 shell 實現它。

你不是故意的sudo -s 'echo test',但是sudo -s echo test。在第一種情況下,sudo 實際上是echo\ test作為 shell 程式碼傳遞的。在類似 Bourne 的 shell 中,它會嘗試執行一個名為'echo test'. 使用rcshell,它將執行一個echo\test作為參數呼叫的命令。

除了 SPC 之外,還有一些字元會sudo轉義。這包括反引號, ;, |, (, ), *, &, =\但奇怪的是,不是(或者這可能是這些/選項$中唯一存在的理由:讓 shell 擴展變數)。-s``--login

它確實包括@。如果您查看程式碼,它會轉義除 ASCII alnum、 和 之外的_所有-字節$

 if (!isalnum((unsigned char)*src) && *src != '_' && *src != '-' && *src != '$')
   *dst++ = '\\';

所以:

sudo -s echo '$@'

sudo實際執行"$SHELL" -c 'echo $\@',這解釋了為什麼在您的範例中,$@不是由啟動的 shellsudo --login 而是由您告訴它執行的 shell 擴展的。

在你的

sudo sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2

sudo執行root的登錄外殼(bash在您的情況下)並告訴它解釋:

sh -c echo\ \"p0\=$0\"\ \&\&\ echo\ \"p1\=$1\"\ \&\&\ echo\ \"p2\=$2\"\ \&\&\ echo\ \"all\=$\@\" sh 1 2

參數已用空格和所有 SPC、"=&@字元連接,但沒有$被轉義。因為那些$沒有被轉義的 bash 登錄 shell 會擴展$0, $1$2但不是$\@因為那個反斜杠。

你可以看到它:

$ SHELL=echo sudo -s sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
-c sh -c echo\ \"p0\=$0\"\ \&\&\ echo\ \"p1\=$1\"\ \&\&\ echo\ \"p2\=$2\"\ \&\&\ echo\ \"all\=$\@\" sh 1 2

所以登錄bashshell最終sh-c第一個參數執行,

echo "p0=-bash" && echo "p1=" && echo "p2=" && echo "all=$@"

作為第二個參數,sh, 1,2作為第 3、第 4 和第 5 個。

要解決它,您可以使用${0}而不是$0, 因為sudo將其轉換為$\{0\}可防止登錄 shell 將其擴展到其內容 $0

$ sudo --login sh -c 'echo "p0=${0}" && echo "p1=${1}" && echo "p2=${2}" && echo "all=${@}"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2

編輯:歷史揭示了其背後的原因

查看程式碼時的更多發現。$顯然是在 2013 年通過這種變化引入了non-escaping 。其中指的是bug#564指的是bug#413

看起來在那個 bug#413 被*“fixed”*之前,sudo表現得和我預期的一樣。

那是:

sudo -i 'some shell code'

讓登錄 shell 解釋該 shell 程式碼(並sudo -s 'some shell code'解釋$SHELL該 shell 程式碼)。但是 bug#413 解決方案破壞了它,因為報告錯誤的人不理解它是這樣工作的。並且錯誤#564 進一步破壞了它(試圖僅恢復錯誤#413 解決方案引入的部分破壞)。

我從 2009 年開始編譯sudo1.7.1 並且-s/-i選項按我預期的那樣工作。1.7.3(具有 bug#413 解析度)的工作方式與您顯然預期的一樣:

$ sudo-1.7.1 -i 'echo "$SHELL"'
/bin/bash
$ sudo-1.7.3 -i 'echo "$SHELL"'
-bash: echo "$SHELL": command not found
$ sudo-1.8.21p2 -i 'echo "$SHELL"'
-bash: echo "/bin/bash": No such file or directory
$ sudo-1.7.3 -i sh -c 'echo "p0=$0" && echo "p1=$1" && echo "p2=$2" && echo "all=$@"' sh 1 2
p0=sh
p1=1
p2=2
all=1 2

在修復該錯誤#413 時引入的轉義很容易被愚弄,因為\在所有字節(而不是字元)前面敲擊 a 並不適用於所有人。

除了rcwhere\甚至不是引用運算符的明顯情況之外,在大多數 shell 中,換行符不能用 引用\

$ SHELL=sh sudo -s echo $'a\nb'
ab
$ SHELL=csh  sudo -s echo $'a\nb'
a b

這也意味著空參數被丟棄:

$ sudo printf '&lt;%s&gt;\n' a '' b
&lt;a&gt;
&lt;&gt;
&lt;b&gt;
$ sudo -s printf '&lt;%s&gt;\n' a '' b
&lt;a&gt;
&lt;b&gt;

\此外,通過在每個byte之前插入 a ,它將字元轉換為字元集中的其他字元,其中\在其他字元中找到字節 0x5c (的編碼):

$ SHELL=bash LC_ALL=zh_HK.big5hkscs sudo -s echo $'\xa3``uname`\xa3`'
bash: line 2: Linuxα: command not found
α

0xa3 0x60 是該語言環境中的希臘 Epsilon。sudo 將 0x60s 更改為 0x5c 0x60 和 0xa3 0x5c 是希臘字母,這就解釋了為什麼uname執行命令。sudo改變了

ε`uname`ε

\α`\`uname\`\α`

當然,行為最終令人驚訝,因為$-, $$, 位置參數和$varname(其中varname是有效的 POSIX 變數名)被擴展,但其他參數(如$@這裡,還有$!, $?, $*, $#)或${varname}or $var[1]( csh/ tcsh/ zsh) 或$var(1)( rc/ es/…)。

引用自:https://unix.stackexchange.com/questions/535925