1つの文字列をbashシェルで少なくとも1つのスペースで区切られた複数の文字列に分割する方法は?


224

各単語の間に少なくとも1つのスペースがある多くの単語を含む文字列があります。文字列を個々の単語に分割して、それらをループできるようにするにはどうすればよいですか?

文字列は引数として渡されます。例えば${2} == "cat cat file"。どうすればループできますか?

また、文字列にスペースが含まれているかどうかを確認するにはどうすればよいですか?


1
どんな殻?Bash、cmd.exe、powershell ...?
Alexey Sviridov

ループする必要があるだけですか(たとえば、各単語に対してコマンドを実行します)?または、後で使用するために単語のリストを保存する必要がありますか?
DVK

回答:


281

文字列変数をforループに渡してみましたか?たとえば、bashは空白で自動的に分割されます。

sentence="This is   a sentence."
for word in $sentence
do
    echo $word
done

 

This
is
a
sentence.

1
@MobRule-これの唯一の欠点は、その後の処理のために出力を簡単にキャプチャできない(少なくとも私は方法を思い出せない)ことです。STDOUTにデータを送信するものについては、下記の「tr」ソリューションを参照してください
DVK

4
変数に追加するだけです:A=${A}${word})
Lucas Jones、

1
$ textを設定します[これにより、単語が$ 1、$ 2、$ 3 ... etcに挿入されます]
Rajesh

32
実際、このトリックは間違った解決策であるだけでなく、シェルのグロビングにより非常に危険です。 期待される代わりにtouch NOPE; var='* a *'; for a in $var; do echo "[$a]"; done出力(読みやすさのためにSPCでLFが置き換えられます)[NOPE] [a] [NOPE][*] [a] [*]
ティノ2015年

@mob特定の文字列に基づいて文字列を分割したい場合はどうすればよいですか?「.xlsx」セパレータの例。

296

個々の要素にアクセスできるように、配列への変換が好きです。

sentence="this is a story"
stringarray=($sentence)

これで、個々の要素に直接アクセスできます(0から始まります)。

echo ${stringarray[0]}

またはループするために文字列に変換し直します:

for i in "${stringarray[@]}"
do
  :
  # do whatever on $i
done

もちろん、文字列を直接ループすることは以前に回答されていましたが、その回答には、後で使用するために個々の要素を追跡しないという欠点がありました。

for i in $sentence
do
  :
  # do whatever on $i
done

Bash配列リファレンス」も参照してください。


26
シェルグロビングのため、残念ながら完全ではありません。予想外のtouch NOPE; var='* a *'; arr=($var); set | grep ^arr=出力arr=([0]="NOPE" [1]="a" [2]="NOPE")arr=([0]="*" [1]="a" [2]="*")
Tino

@Tino:グロビングに干渉させたくない場合は、単にオフにします。その後、このソリューションはワイルドカードでも問題なく機能します。それは私の意見では最良のアプローチです。
Alexandros

3
@Alexandros私のアプローチは、デフォルトで安全であり、あらゆる状況で完全に機能するパターンのみを使用することです。安全なソリューションを取得するためにシェルグロビングを変更する必要性は、非常に危険なパス以上のものであり、それはすでにダークサイドです。ですから、私のアドバイスは、ここでこのようなパターンを使用することに慣れないようにすることです。遅かれ早かれ、詳細を忘れて、誰かがあなたのバグを悪用するからです。そのようなエクスプロイトの証拠はプレスにあります。すべて。シングル。日。
Tino、

86

組み込みの「set」シェルを使用するだけです。例えば、

$ textを設定

その後、$ textの個々の単語は$ 1、$ 2、$ 3などになります。堅牢性のために、通常は

セット-ジャンク$ text
シフト

$ textが空の場合やダッシュで始まる場合に対処します。例えば:

text = "これはテストです"
セット-ジャンク$ text
シフト
一言; 行う
  エコー "[$ word]"
終わった

これはプリント

[この]
[は]
[a]
[テスト]

5
これは、個々のパーツに直接アクセスできるように変数を分割する優れた方法です。+1; 私の問題を解決しました
Cheekysoft

私は使用を提案するつもりでしawksetが、はるかに簡単です。私は今setファンボーイです。@Idelicに感謝!
Yzmir Ramirez 2012

22
そのようなことをする場合はシェルのグロブに注意してください:期待されたものの代わりにtouch NOPE; var='* a *'; set -- $var; for a; do echo "[$a]"; done出力します。 分割された文字列にSHELLメタ文字がないことが101%わかっている場合にのみ使用してください。[NOPE] [a] [NOPE][*] [a] [*]
ティノ2015年

4
@Tino:その問題だけでなく、ここでは、どこにでも適用されますが、この場合、あなたは可能性だけのset -fset -- $varset +f後で無効グロブへ。
Idelic

3
@Idelic:良いキャッチ。set -fあなたのソリューションも安全です。しかしset +f、これは各シェルのデフォルトです。したがって、他の人はおそらくそれを認識していないので(私もそうでした)、これは重要な詳細であり、注意する必要があります。
ティノ

81

BASH 3以降でおそらく最も簡単で安全な方法は次のとおりです。

var="string    to  split"
read -ra arr <<<"$var"

arr文字列の分割された部分を取得する配列です)または、入力に改行が含まれている可能性があり、最初の行だけではない場合:

var="string    to  split"
read -ra arr -d '' <<<"$var"

(のスペースに注意してください-d ''、それを残すことはできません)が、予期しない改行が発生する可能性があります<<<"$var"(これにより暗黙的に末尾にLFが追加されるため)。

例:

touch NOPE
var="* a  *"
read -ra arr <<<"$var"
for a in "${arr[@]}"; do echo "[$a]"; done

期待される出力

[*]
[a]
[*]

このソリューション(ここでの以前のすべてのソリューションとは対照的)は、予期しない、しばしば制御不能なシェルグロビングを起こしにくいためです。

また、これにより、IFSの機能を最大限に活用できます。

例:

IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
for a in "${arr[@]}"; do echo "[$a]"; done

次のような出力:

[tino]
[x]
[1000]
[1000]
[Valentin Hilbig]
[/home/tino]
[/bin/bash]

ご覧のとおり、スペースもこの方法で保存できます。

IFS=: read -ra arr <<<' split  :   this    '
for a in "${arr[@]}"; do echo "[$a]"; done

出力

[ split  ]
[   this    ]

IFSBASH でのの処理はそれ自体がテーマであるため、テストを行ってください。これに関する興味深いトピックがいくつかあります。

  • unset IFS:SPC、TAB、NLの実行とオンラインの開始と終了を無視します
  • IFS='':フィールド分離なし、すべてを読み取るだけ
  • IFS=' ':SPCの実行(およびSPCのみ)

最後の例

var=$'\n\nthis is\n\n\na test\n\n'
IFS=$'\n' read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

出力

1 [this is]
2 [a test]

ながら

unset IFS
var=$'\n\nthis is\n\n\na test\n\n'
read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

出力

1 [this]
2 [is]
3 [a]
4 [test]

ところで:

  • $'ANSI-ESCAPED-STRING'慣れていなければ、時間の節約になります。

  • を含めない場合-r(などread -a arr <<<"$var")、readはバックスラッシュエスケープを実行します。これは読者のための演習として残されています。


2番目の質問について:

これcaseは複数のケースを一度にチェックできるため(通常、caseは最初の一致のみを実行し、フォールスルーがmultiplce caseステートメントを使用する必要がある場合)、この必要性がよくあるケースです(pun意図されました):

case "$var" in
'')                empty_var;;                # variable is empty
*' '*)             have_space "$var";;        # have SPC
*[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
*[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
*[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
*)                 default_case "$var";;      # if all above does not match
esac

したがって、戻り値を設定して、次のようにSPCをチェックできます。

case "$var" in (*' '*) true;; (*) false;; esac

なんでcase?通常、正規表現シーケンスよりも少し読みやすく、シェルメタキャラクターのおかげで、すべてのニーズの99%を非常にうまく処理します。


2
この回答は、強調されたグロビングの問題とその包括性のため、より多くの投票に値します
Brian Agnew

@brianありがとう。set -fまたはset -o noglobを使用して、シェルのメタ文字がこのコンテキストで害を及ぼすことがないように、グロビングを切り替えることができます。しかし、私は実際にはその友人ではありません。これはシェルの多くのパワーを残しているため、この設定を切り替えるとエラーが発生しやすくなります。
Tino、

2
すばらしい答えです。確かに、より多くの賛成投票に値します。ケースのフォールスルーに関するサイドノート-あなたはそれを;&達成するために使用できます。登場したbashのバージョンがよくわからない。私は4.3ユーザーです
セルギーコロディアズニー

2
私はこれをまだ知らなかったので、@ Sergは注意してくれてありがとう!調べたところ、Bash4に登場しました;&Cのようにパターンチェックなしの強制フォールスルー;;&です。さらに、パターンチェックを続けているものもあります。だから、;;のようなものif ..; then ..; else if ..;;&似ているif ..; then ..; fi; if ..ところ、;&似ているm=false; if ..; then ..; m=:; fi; if $m || ..; then ..- 1(他から)学習を停止したことがない;)
ティノ

@Tinoそれは絶対に本当です-学習は継続的なプロセスです。実際、私は;;&あなたがコメントする前に知りませんでした :Dありがとう、そしてシェルがあなたと一緒にいるかもしれません;)
Sergiy Kolodyazhnyy

43
$ echo "This is   a sentence." | tr -s " " "\012"
This
is
a
sentence.

スペースを確認するには、grepを使用します。

$ echo "This is   a sentence." | grep " " > /dev/null
$ echo $?
0
$ echo "Thisisasentence." | grep " " > /dev/null     
$ echo $?
1

1
BASHではecho "X" |通常<<<"X"、次のようにに置き換えることができますgrep -s " " <<<"This contains SPC"。とecho X | read varは対照的に何かをすると、違いを見つけることができますread var <<< X。後者のみ輸入変数var第一の変形でそれにアクセスする一方で、現在のシェルに、次のことを行う必要があり、このようなグループ:echo X | { read var; handle "$var"; }
ティノ

17

(A)文を単語に分割するには(スペースで区切る)、次のコマンドを使用してデフォルトのIFSを使用できます

array=( $string )


次のスニペットを実行する

#!/bin/bash

sentence="this is the \"sentence\"   'you' want to split"
words=( $sentence )

len="${#words[@]}"
echo "words counted: $len"

printf "%s\n" "${words[@]}" ## print array

出力されます

words counted: 8
this
is
the
"sentence"
'you'
want
to
split

ご覧のとおり、問題なく一重引用符または二重引用符を使用することもできます

注:
-これは基本的にmobの回答と同じですが、この方法で配列を格納し、さらに必要な場合に使用できます。単一のループのみが必要な場合は、彼の回答を使用できます。これは1行短い:)
- 区切り文字に基づいて文字列を分割する別の方法については、この質問を参照しください。


(B)文字列内の文字をチェックするために、正規表現の一致を使用することもできます。
使用できる空白文字の有無を確認する例:

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi

正規表現のヒント(B)の場合は+1ですが、誤ったソリューション(A)の場合は-1です。これは、シェルグロビングが発生しやすいエラーであるためです。;)
Tino、


1
echo $WORDS | xargs -n1 echo

これにより、すべての単語が出力されます。その後、必要に応じてリストを処理できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.