web-dev-qa-db-ja.com

bashで(コマンド引数のように)引用符で文字列を分割する方法は?

私はこのような文字列を持っています:

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

次のように分割できるようにしたい:

aString that may haveSpaces IN IT
bar
foo
bamboo  
bam boo

それ、どうやったら出来るの? (できればワンライナーを使用)

8
foxneSs

デビッド・ポスティルの答えを見たとき、「もっと簡単な解決策があるはずだ」と思いました。いくつかの実験の後、私は次の作品を見つけました:-

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
3
AFH

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

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

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 /)!

それを保護する非常に非常に単純な試みは、backtrick `および$のような文字を置き換えるだけです。

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メソッドで非常に欲しいが、コンテンツを解釈しない

2
Hubbitus

それ、どうやったら出来るの?

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

文字列がbash変数にある場合はどうすればよいですか?

bash文字列トークナイザを使用する単純なアプローチは機能しません。引用符の外側のスペースだけでなく、すべてのスペースで分割されるためです。

DavidPostill@Hal /f/test
$ cat ./test.sh
#! /bin/bash
string='"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"'
for Word in $string; do echo "$Word"; done

DavidPostill@Hal /f/test
$ ./test.sh
"aString
that
may
haveSpaces
IN
IT"
bar
foo
"bamboo"
"bam
boo"

これを回避するために、次のシェルスクリプト(splitstring.sh)は1つのアプローチを示しています。

#! /bin/bash 
string=$(cat <<'EOF'
"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo" 
EOF
)
echo Source String: "$string"
results=()
result=''
inside=''
for (( i=0 ; i<${#string} ; i++ )) ; do
    char=${string:i:1}
    if [[ $inside ]] ; then
        if [[ $char == \\ ]] ; then
            if [[ $inside=='"' && ${string:i+1:1} == '"' ]] ; then
                let i++
                char=$inside
            fi
        Elif [[ $char == $inside ]] ; then
            inside=''
        fi
    else
        if [[ $char == ["'"'"'] ]] ; then
            inside=$char
        Elif [[ $char == ' ' ]] ; then
            char=''
            results+=("$result")
            result=''
        fi
    fi
    result+=$char
done
if [[ $inside ]] ; then
    echo Error parsing "$result"
    exit 1
fi

echo "Output strings:"
for r in "${results[@]}" ; do
    echo "$r" | sed "s/\"//g"
done

出力:

$ ./splitstring.sh
Source String: "aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"
Output strings:
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

ソース:StackOverflowの回答 引用符の外側のスペースで文字列を分割 by choroba 。スクリプトは、質問の要件に合わせて調整されています。

2
DavidPostill

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
1
Olivier

awkを使用する

echo '"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"' | awk 'BEGIN {FPAT = "([^ ]+)|(\"[^\"]+\")"}{for(i=1;i<=NF;i++){gsub("\"","",$i);print $i} }'
aString that may haveSpaces IN IT
bar
foo
bamboo
bam boo

または、スペースを「%20」または「_」に変換して、次のコマンドthrow pipで処理できるようにします。

echo '"aString that may haveSpaces IN IT" bar foo "bamboo" "bam boo"' | awk 'BEGIN {FPAT = "([^ ]+)|(\"[^\"]+\")"}{for(i=1;i<=NF;i++){gsub("\"","",$i);gsub(" ","_",$i)} print }'
aString_that_may_haveSpaces_IN_IT bar foo bamboo bam_boo

参照: Awkは二重引用符で囲まれた文字列を1つのトークンと見なし、その間のスペースを無視します

0
tinyhare