bash で引用符付きの文字列 (コマンド引数など) を分割するにはどうすればよいですか?

bash で引用符付きの文字列 (コマンド引数など) を分割するにはどうすればよいですか?

次のような文字列があります:

"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"

次のように分割できるようにしたいと思います。

aString that may haveSpaces IN IT
bar
foo
bamboo  
bam boo

どうすればいいでしょうか? (できればワンライナーを使用)

答え1

David Postill の回答を見たとき、「もっと簡単な解決策があるはずだ」と思いました。いくつか実験してみた後、次の方法を見つけました:-

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
echo $string
eval 'for word in '$string'; do echo $word; done'

これは、結果の行 (インラインの回答) を実行する前に、eval行を展開 (引用符を削除して を展開) するため機能します。string

for word in "aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"; do echo $word; done

同じ行に展開される代替手段は次のとおりです。

eval "for word in $string; do echo \$word; done"

ここではstring二重引用符内では が展開されますが、行が実行される前に が展開されない$ようにエスケープする必要がありますword(他の形式では、一重引用符を使用しても同じ効果があります)。結果は次のようになります。

[~/]$ string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
[~/]$ echo $string
"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"
[~/]$ eval 'for word in '$string'; do echo $word; done'
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo
[~/]$ eval "for word in $string; do echo \$word; done"
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

答え2

最も簡単な解決策は、引用符で囲まれた引数の配列を作成し、必要に応じてループしたり、コマンドに直接渡したりすることです。

eval "array=($string)"

for arg in "${array[@]}"; do echo "$arg"; done   

ps より簡単な方法を見つけた場合はコメントしてくださいeval

編集:

@Hubbitus の回答を基に、完全にサニタイズされ、適切に引用されたバージョンを作成しました。注: これはやり過ぎで、ほとんどの句読点の前の二重引用符または一重引用符で囲まれたセクションに追加のバックスラッシュが残りますが、攻撃に対しては無敵です。

declare -a "array=($( echo "$string" | sed 's/[][`~!@#$%^&*():;<>.,?/\|{}=+-]/\\&/g' ))"

興味のある読者は、必要に応じて修正してください。http://ideone.com/FUTHhj

答え3

xargs はそれをかなりうまく実行できるようです:

$ a='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
$ printf "%s" "$a" | xargs -n 1 printf "%s\n"
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

答え4

declareの代わりに を使用することもできますeval。例:

の代わりに:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
echo "Initial string: $string"
eval 'for word in '$string'; do echo $word; done'

する:

declare -a "array=($string)"
for item in "${array[@]}"; do echo "[$item]"; done

ただし、入力がユーザーから行われる場合は、それほど安全ではないことに注意してください。

したがって、次のような文字列で試してみると:

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'

評価されますhostname(もちろん のようなものもあるかもしれませんrm -rf /)!

これを防ぐための非常に単純な試みは、バックトリック ` や $ などの文字を置き換えるだけです。

string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" `hostname`'
declare -a "array=( $(echo $string | tr '`$<>' '????') )"
for item in "${array[@]}"; do echo "[$item]"; done

出力は次のようになります:

[aString that may haveSpaces IN IT]
[bar]
[foo]
[bamboo]
[bam boo]
[?hostname?]

方法と長所と短所に関する詳細は、次の優れた回答で確認できます。https://stackoverflow.com/questions/17529220/why-should-eval-be-avoided-in-bash-and-what-should-i-use-instead/17529221#17529221

しかし、攻撃の余地はまだ残されています。 私は、文字列を二重引用符(")のように引用するbashメソッドを、内容を解釈せずに使用したいと考えています。

関連情報