web-dev-qa-db-ja.com

Wordの分割を避けるために、文字列内の変数展開を引用するにはどうすればよいですか?

$ myvar="/path to/my directory"
$ Sudo bash -c "cd $myvar"

そのような場合、どのように引用できますか$myvarmyvarの値に空白があるためにWordの分割を回避するには?

9
Tim

_$myvar_は引用符で囲まれていないため、そのコードにはWord splitting(引用符で囲まれていない展開時に変数を分割する機能のように)はありません。

ただし、_$myvar_がbashに渡される前に展開されるため、コマンドインジェクションの脆弱性があります。したがって、その内容はbashコードとして解釈されます!

そこにスペースがあると、いくつかの引数がcdに渡されます。これはWord splittingのためではなく、シェル構文でいくつかのトークンとして解析されるためです。 _bye;reboot_の値を指定すると、再起動します!¹

ここでは、次のようにします。

_Sudo bash -c 'cd -P -- "$1"' bash "$myvar"
_

(ここで、インラインスクリプトの最初の引数として_$myvar_の内容を渡します。IFSワード分割を防ぐために、それぞれのシェルに対して_$myvar_と_$1_の両方が引用されていることに注意してください(そしてグロビング))。

または:

_Sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'
_

(環境変数で_$myvar_の内容を渡す場合)。

もちろん、インラインスクリプトでonlycdを実行しても、役立つものは何もありません(ただし、rootcdにアクセスできるかどうかを確認する以外)。おそらく、そのスクリプトでcdを実行し、次のような何かを実行したいとします。

_Sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"
_

Sudoを使用して、他の方法ではアクセスできないディレクトリにcdをアクセスできるようにする意図がある場合、それは実際には機能しません。

_Sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"
_

_$myvar_の現在のディレクトリでインタラクティブなbashを開始します。しかし、そのシェルはrootとして実行されます。

あなたがすることができます:

_Sudo sh -c 'cd -P -- "$1" && exec Sudo -u "$Sudo_USER" bash' sh "$myvar"
_

現在のディレクトリが_$myvar_である非特権インタラクティブbashを取得するには、最初にそのディレクトリにcdへのアクセス許可がないと、そのディレクトリでは何もできません現在の作業ディレクトリです。

_$ myvar=/var/spool/cron/crontabs
$ Sudo sh -c 'cd -P -- "$1" && exec Sudo -u "$Sudo_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied
_

例外は、ディレクトリ自体に対するsearch権限があるが、そのパスのディレクトリコンポーネントの1つに対する権限がない場合です。

_$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ Sudo sh -c 'cd -P -- "$1" && exec Sudo -u "$Sudo_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied
_

¹厳密に言えば、$(seq 10)(文字通り)のような_$myvar_の値の場合、bashシェルによるコマンド置換の展開時にWord分割が当然発生し、rootとして開始されます

13

新しい(bash 4.4)引用マジックがあり、より直接的にやりたいことができます。より大きなコンテキストによっては、他の手法のいずれかを使用する方が良い場合がありますが、この限られたコンテキストで機能します。

Sudo bash -c "cd ${myvar@Q}; pwd"
4
Seth Robertson

$myvarの内容を信頼できない場合は、GNU printfを使用してエスケープバージョンを作成するのが妥当なアプローチです。

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

printfのmanページにあるように:

%q

ARGUMENTは、シェル入力として再利用できる形式で出力され、提案されたPOSIX $''構文を使用して、出力できない文字をエスケープします。

4
Toby Speight

StéphaneChazelasの提案は、間違いなくこれを行うための最良の方法です。 R ..の回答のようにsedサブプロセスを使用するのではなく、パラメーター展開構文で安全に引用する方法についての回答を提供します。

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

そのスクリプトの出力は次のとおりです。

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar
3
JoL

まず最初に、他の人がcdコマンドはすぐに終了するシェルのコンテキストで発生するため、役に立たないことに気づきました。しかし、一般的にこの質問は理にかなっています。必要なのは、Shellで任意の文字列を引用する方法であり、Shellで引用されたものとして解釈されるコンテキストで使用できます。ストリング。私の shトリック ページにこの例があります:

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

この関数を使用すると、次のことができます。

myvar="/path to/my directory"
Sudo bash -c "cd $(quote "$myvar")"

はい、単語分割があります。
まあ、技術的には、bashで行を解析する際のコマンドライン分割。

最初に引用を正しくしましょう:

私があなたがそれを望んでいると私が思うようにコマンドを機能させるための最も簡単な(そして安全でない)解決策は次のとおりです:

bash -c "cd \"$myvar\""

何が起こるか確認してみましょう(myvar="/path to/my directory"を想定):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

ご覧のとおり、パス文字列はスペースで区切られています。そして:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

分割されていません。

ただし、コマンドは(書かれているとおり)安全ではありません。より良い使用:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

これにより、ダッシュ(-)で始まるファイルまたは問題を引き起こす可能性のあるリンクをたどるファイルからcdが保護されます。

$myvarの文字列がパスであると信頼できる場合、これは機能します。
ただし、「文字列を実行」しているため、「コードインジェクション」は引き続き可能です。

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

次のように、文字列をより強く引用する必要があります。

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

上記のように、値は解釈されず、"$1'としてのみ使用されました。

これで(安全に)Sudoを使用できます。

$ Sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

引数が複数ある場合は、次のものを使用できます。

$ Sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"
1
Isaac