Bashの区切り文字で文字列を分割するにはどうすればよいですか?


2042

私はこの文字列を変数に格納しています:

IN="bla@some.com;john@home.com"

次に、文字列を;区切り文字で分割して、次のようにします。

ADDR1="bla@some.com"
ADDR2="john@home.com"

ADDR1ADDR2変数は必ずしも必要ではありません。それらが配列の要素である場合は、さらに優れています。


以下の回答からの提案の後、私は次のようになりました:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

出力:

> [bla@some.com]
> [john@home.com]

Internal_field_separator(IFS)をに設定することを含む解決策がありました;。その答えで何が起こったのかわかりませんが、どうやっIFSてデフォルトにリセットしますか?

RE:IFS解決策、私はこれを試してみましたが、うまくいきました。古いものを保存してからIFS復元します。

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

ところで、私が試したとき

mails2=($IN)

ループで印刷するとき、最初の文字列のみを取得しました$IN


14
「Edit2」に関して:「IFSの設定を解除する」だけで、デフォルトの状態に戻ります。既にデフォルト以外の値に設定されていることを予期する何らかの理由がない限り、明示的に保存して復元する必要はありません。さらに、これを関数内で実行している場合(そうでない場合は、なぜでしょうか?)、IFSをローカル変数として設定すると、関数を終了すると以前の値に戻ります。
Brooks Moses

19
@BrooksMoses:(a)local IFS=...可能な場合は使用するための+1 。(b)-1の場合unset IFS、これはIFSをデフォルト値に正確にリセットしませんが、未設定のIFSはIFSのデフォルト値($ '\ t \ n')と同じように動作すると思いますが、 IFSをカスタム値に設定してコードが呼び出されることはないと盲目的に想定しています。(c)別のアイデアは、サブシェルを呼び出す(IFS=$custom; ...)ことです。サブシェルが終了すると、IFSは元の状態に戻ります。
dubiousjim

実行可能ファイルをスローする場所を決定するためのパスを簡単に確認したいだけなので、実行することにしましたruby -e "puts ENV.fetch('PATH').split(':')"。純粋なbashを維持したい場合は役に立ちませんが、スプリットが組み込まれているスクリプト言語を使用する方が簡単です。
nicooga

4
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
user2037659

2
それを配列として保存するには、別の括弧のセットを配置して\n、スペースを1つだけ変更する必要がありました。つまり、最後の行はmails=($(echo $IN | tr ";" " "))です。これでmails、配列表記を使用するmails[index]か、ループで反復するだけで、の要素を確認できます
afranques

回答:


1236

内部フィールド区切り記号(IFS)変数を設定して、解析して配列にすることができます。これがコマンドで発生すると、への割り当てIFSはその単一のコマンドの環境(へread)に対してのみ行われます。次に、IFS変数値に従って入力を解析して配列に変換し、それを反復処理できます。

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

で区切られた1行の項目を解析;し、配列にプッシュします。全体の処理のためのもの$INで区切られた入力のたびに1行;

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"

22
これがおそらく最良の方法です。IFSは現在の値でどのくらいの期間存続しますか。それが不必要なときに設定されてコードをめちゃくちゃにすることはありますか、それが終わったらどのようにリセットできますか?
Chris Lutz、

7
修正が適用された後、読み取りコマンドの期間内のみ:)
Johannes Schaub-litb 2009年

14
whileループを使用しなくても、すべてを一度に読み取ることができます。read-r -d '' -a addr <<< "$ in"#ここで-d ''が重要です。最初の改行で停止しないようにreadに指示します(これはデフォルトの-d)ですが、EOFまたはNULLバイト(バイナリデータでのみ発生)まで続行します。
2009年

55
@LucaBorrione IFSと同じ行に設定するreadと、セミコロンや他の区切り文字なしで、別のコマンドではなく、そのコマンドにスコープが設定されるため、常に「復元」されます。手動で何もする必要はありません。
Charles Duffy

5
@imagineerThis $IN引用符を付ける必要があるヘレストリングとIFSのローカル変更に関連するバグがあります。このバグはbash4.3 で修正されています。
chepner 2014年

973

Bashシェルスクリプトの分割配列から取得

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })

説明:

この構造は、文字列内のすべての出現';'(最初の//意味はグローバル置換)IN' '(単一のスペース)で、スペースで区切られた文字列を配列として解釈します(それが括弧で囲まれています)。

中括弧の内側で各';'文字を文字に置き換えるために使用される構文' 'は、パラメーター展開と呼ばれます。ます。

一般的な問題がいくつかあります。

  1. 元の文字列にスペースがある場合は、IFSを使用する必要があります。
    • IFS=':'; arrIN=($IN); unset IFS;
  2. 元の文字列にスペースがあり、区切り文字が改行の場合、IFSを次のように設定できます。
    • IFS=$'\n'; arrIN=($IN); unset IFS;

84
追加したいのは、これが最も簡単な方法です。$ {arrIN [1]}で配列要素にアクセスできます(もちろんゼロから開始)
Oz123

26
発見:$ {}内の変数を変更する手法は、「パラメーター拡張」として知られています。
KomodoDave 2012年

23
いいえ、スペースが存在する場合、これは機能しないと思います... '、'を ''に変換し、スペースで区切られた配列を構築します。
イーサン

12
非常に簡潔ですが、一般的な使用には注意が必要です。シェルは文字列に単語の分割拡張を適用しますが、これは望ましくない場合があります。試してみてください。IN="bla@some.com;john@home.com;*;broken apart"。つまり、トークンにスペースや文字が埋め込まれている場合、このアプローチは機能しません。など、*その現在のフォルダ内のトークンの一致ファイル名を作るために起こります。
mklement0 2013

53
これは、他の理由のために悪いアプローチである:例えば、あなたの文字列が含まれている場合は;*;、その後、*現在のディレクトリ内のファイル名のリストに展開されます。-1
Charles Duffy

249

すぐに処理してもかまわない場合は、次のようにします。

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

この種のループを使用して配列を初期化することもできますが、おそらくもっと簡単な方法があります。これが役に立てば幸いです。


IFSの回答はそのままにしておく必要があります。それは私が知らないことを教えてくれました、そしてそれは間違いなく配列を作りましたが、これは単に安い代用品を作るだけです。
Chris Lutz、

そうですか。ええ、私はこれらのばかげた実験をしているのを見つけます、私が問題に答えようとするたびに新しいことを学びます。#bash IRCフィードバックに基づいて編集し、元に戻しました:)
Johannes Schaub-litb 2009年

33
-1は、コード分割に2つのバグを導入しているため、ワードスプリットに気付いていないことは明らかです。1つは$ INを引用しない場合で、もう1つは改行が単語分割で使用される唯一の区切り文字であると偽る場合です。すべての行ではなく、IN内のすべてのWORDを繰り返し処理しています。すべての要素がセミコロンで区切られているわけではありませんが、動作するように見えるという副作用があるように見える場合があります。
lhunath 2009年

3
"$ IN"をエコーするように変更できます| tr ';' '\ n' | 読み取り中-r ADDY; #「$ ADDY」を処理します。彼を幸運にするためにやった、と思います:)これはforkし、ループ内から外部変数を変更できないことに注意してください(そのため、<<< "$ IN"構文を使用しました)
Johannes Schaub-litb

8
コメントでの議論の要約:一般的な使用に関する警告:シェルは文字列に単語の分割展開を適用しますが、これは望ましくない場合があります。試してみてください。IN="bla@some.com;john@home.com;*;broken apart"。つまり、トークンにスペースや文字が埋め込まれている場合、このアプローチは機能しません。など、*その現在のフォルダ内のトークンの一致ファイル名を作るために起こります。
mklement0 2013

202

互換性のある答え

これを行うには多くの異なる方法があります

ただし、bash多くの特別な機能(いわゆるバシズム)があることに最初に注意することが重要ですには他の機能では動作しない)があること

特に、この投稿のソリューションやスレッド内の他のソリューションで使用されている配列連想配列パターン置換バシズムであり、多くの人が使用する他のシェルでは機能しない可能性があります。

たとえば、私のDebian GNU / Linuxには、標準があります。シェルと呼ばれるものがあります。; と呼ばれる別のシェルを使用したい多くの人々を知っています; と呼ばれる特別なツールもあります 彼自身のシェルインタープリター()。

リクエストされた文字列

上記の質問で分割される文字列は次のとおりです。

IN="bla@some.com;john@home.com"

この文字列の変更バージョンを使用して、私のソリューションが空白を含む文字列に対して堅牢であることを確認します。

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

区切り文字に基づいて文字列を分割 (バージョン> = 4.2)

純粋な bash、我々が作成することができ、アレイのための一時的な値で要素分割とをIFS入力フィールドセパレータ)。とりわけ、IFSはbash、配列を定義するときに要素間の区切り文字としてどの文字を処理するかを指示します。

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS

の新しいバージョンでbashは、コマンドの前にIFS定義を付けると、そのコマンドのIFS のみが変更され、その後すぐに以前の値にリセットされます。これは、上記を1行で実行できることを意味します。

IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'

文字列INfieldsセミコロンで分割されたという名前の配列に格納されていることがわかります。

set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'

(これらの変数の内容をdeclare -p:を使用して表示することもできます。)

declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")

readある最速全く存在しないため、分割を行う方法フォークと呼ばれる、または外部のリソースが。

配列を定義したら、単純なループを使用して、各フィールド(または、定義した配列内の各要素)を処理できます。

# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
    echo "> [$x]"
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

または、シフトアプローチを使用して処理した後、配列から各フィールドを削除することもできます。

while [ "$fields" ] ;do
    echo "> [$fields]"
    # slice the array 
    fields=("${fields[@]:1}")
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

配列の単純な出力だけが必要な場合は、ループする必要さえありません。

printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

更新:最近 > = 4.4

の新しいバージョンではbash、次のコマンドで遊ぶこともできますmapfile

mapfile -td \; fields < <(printf "%s\0" "$IN")

この構文は、特殊文字、改行、空のフィールドを保持します!

空のフィールドを含めたくない場合は、次のようにします。

mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}")   # drop '\n' added by '<<<'

を使用mapfileすると、配列の宣言をスキップして、区切られた要素を暗黙的に「ループ」し、それぞれに対して関数を呼び出すこともできます。

myPubliMail() {
    printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
    # mail -s "This is not a spam..." "$2" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail

(注:\0フォーマット文字列の最後にある空のフィールドが気にならないか、存在しない場合は、フォーマット文字列の最後は役に立ちません。)

mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail

# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

または、を使用<<<して、関数本体に、追加した改行を削除するための処理を含めることができます。

myPubliMail() {
    local seq=$1 dest="${2%$'\n'}"
    printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
    # mail -s "This is not a spam..." "$dest" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile <<<"$IN" -td \; -c 1 -C myPubliMail

# Renders the same output:
# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

区切り文字に基づいて文字列を分割

を使用できない場合bash、または多くの異なるシェルで使用できるものを作成する場合bashismを使用できないことよくあります。これには、上記のソリューションで使用していた配列が含まれます。

ただし、文字列の「要素」をループするために配列を使用する必要はありません。多くのシェルでは、パターンの最初または最後の出現から文字列の部分文字列を削除するために使用される構文があります。注*ゼロ以上の文字を表しますワイルドカードです。

(これまでに投稿されたソリューションでこのアプローチが欠如していることが、この回答を書いている主な理由です。)

${var#*SubStr}  # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*}  # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string

Score_Underで説明したように

#そして%、文字列の最初最後から、可能な限り最短の一致する部分文字列を削除します。

##そして%%、最長の一致する部分文字列を削除します。

上記の構文を使用して、デリミタまでまたはデリミタの後のサブストリングを削除することにより、ストリングからサブストリング「要素」を抽出するアプローチを作成できます。

以下のコードブロックは、 (Mac OSを含む bash)、、および

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
    # extract the substring from start of string up to delimiter.
    # this is the first "element" of the string.
    iter=${IN%%;*}
    echo "> [$iter]"
    # if there's only one element left, set `IN` to an empty string.
    # this causes us to exit this `while` loop.
    # else, we delete the first "element" of the string from IN, and move onto the next.
    [ "$IN" = "$iter" ] && \
        IN='' || \
        IN="${IN#*;}"
  done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

楽しんで!


15
###%、及び%%置換は、IMO(彼らは削除どのくらいのために)覚えやすい説明であるものを持っている。#そして%、最短一致する文字列を削除し、##そして%%可能な限り最長を削除します。
Score_Under

1
IFS=\; read -a fields <<<"$var"改行に失敗し、末尾の改行を追加します。他の解決策は、末尾の空のフィールドを削除します。
Isaac、

シェル区切り文字は、最もエレガントな答え、ピリオドです。
エリックチェン

最後の選択肢は、どこか別の場所に設定されたフィールドセパレータのリストで使用できますか?たとえば、これをシェルスクリプトとして使用し、フィールドセパレーターのリストを位置パラメーターとして渡します。
sancho.s ReinstateMonicaCellio 2018

はい、ループ内:for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ...
F. Hauri

184

cutコマンドを参照するいくつかの回答を見ましたが、それらはすべて削除されました。特に、区切られたログファイルを解析するために、この種のことを実行するためのより便利なコマンドの1つだと思うので、誰もそれについて詳しく説明していないのは少し奇妙です。

この特定の例をbashスクリプト配列に分割する場合、trおそらくより効率的ですがcut、使用でき、特定のフィールドを途中からプルしたい場合により効果的です。

例:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

明らかにそれをループに入れ、-fパラメーターを反復して各フィールドを個別にプルすることができます。

これは、次のような行を含む区切られたログファイルがある場合により便利になります。

2015-04-27|12345|some action|an attribute|meta data

cutcatこのファイルを使用して、さらに処理するために特定のフィールドを選択するには、非常に便利です。


6
を使用cutしたことに対する称賛、それは仕事に適したツールです!これらのシェルハックのどれよりもはるかにクリアされました。
MisterMiyagi 2016年

4
このアプローチは、要素の数が事前にわかっている場合にのみ機能します。あなたはそれを取り巻くいくつかのより多くのロジックをプログラムする必要があるでしょう。また、すべての要素に対して外部ツールを実行します。
uli42

私はcsvで空の文字列を避けようと探していました。これで、正確な「列」の値も指定できます。ループで既に使用されているIFSを操作します。私の状況で予想以上に良い。
Louis Loudog Trottier、2018

あまりにすなわちIDとPIDを引っ張るために非常に便利
ミロスGrujic

この答えは、半ページスクロールダウンする価値があります:)
Gucu112

124

これは私のために働きました:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

1
これは単一の文字区切り文字でのみ機能しますが、OPが探していたものです(セミコロンで区切られたレコード)。
GuyPaddock 2018

約4年前に@Ashokが回答し、さらに@DougWが1年以上前に回答した場合よりも回答が多く、さらに詳しい情報が提供されています。他のソリューションとは異なるソリューションを投稿してください。
MAChitgarha

90

このアプローチはどうですか:

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

ソース


7
+1 ...しかし、変数に "Array"という名前は付けません...わかりません。良い解決策。
Yzmir Ramirez

14
+1 ...しかし、「セット」と宣言-aは不要です。あなたにもちょうど使用している可能性がIFS";" && Array=($IN)
ATA

+1副次的注意のみ:古いIFSを保持してから復元することはお勧めできませんか?(stefanBの編集3で示されているように)ここに着陸した人々(ソリューションをコピーして貼り付けることもある)はこれについて考えないかもしれません
Luca Borrione

6
-1:まず、@ ataは、このコマンドのほとんどが何もしないことは正しいです。次に、単語分割を使用して配列を形成します。その際、グロブ拡張を禁止することは何もしません(したがって、配列要素のいずれかにグロブ文字がある場合、それらの要素は一致するファイル名に置き換えられます)。
Charles Duffy

1
使用を提案$'...'IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'。次にecho "${Array[2]}"、改行付きの文字列を出力します。set -- "$IN"この場合も必要です。はい、グロブの拡大を防ぐため、ソリューションにはを含める必要がありますset -f
John_West 2016年

79

AWKは問題を解決するための最良かつ効率的なコマンドだと思います。AWKは、ほとんどすべてのLinuxディストリビューションにデフォルトで含まれています。

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

あげる

bla@some.com john@home.com

もちろん、awk印刷フィールドを再定義することで、各メールアドレスを保存できます。


3
またはさらにシンプル:echo "bla@some.com; john@home.com" | awk 'BEGIN {RS = ";"} {print}'
Jaro

@Jaroこれは、コンマを含む文字列があり、それを行に再フォーマットする必要があったときに、私にとって完璧に機能しました。ありがとう。
Aquarelle 2014年

このシナリオで機能しました-> "echo" $ SPLIT_0 "| awk -F 'inode =' '{print $ 1}'"!文字( ";")の代わりに環( "inode =")を使用しようとすると問題が発生しました。$ 1、$ 2、$ 3、$ 4が配列の位置として設定されます!配列を設定する方法がある場合...より良いです!ありがとう!
Eduardo Lucio

@EduardoLucio、私が考えていることは多分あなたが最初にあなたの区切り文字を置き換えることができているinode=;などによってsed -i 's/inode\=/\;/g' your_file_to_process定義し、その後、-F';'適用する際にawk、あなたを助けることができる希望を。
トン

66
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

4
-1 文字列にスペースが含まれている場合はどうなりますか?たとえばIN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )、この場合、2(セミコロンで区切られた各行の要素)ではなく、8つの要素の配列(単語スペースごとに区切られた要素)が生成されます
Luca Borrione

3
@Lucaいいえ、sedスクリプトは正確に2行を作成します。複数のエントリを作成するのは、それをbash配列(デフォルトでは空白で分割される)に配置したときです
lothar

それがまさにポイントです。OPは、編集結果を見るとわかるように、ループでループするために配列にエントリを格納する必要があります。あなたの(良い)答えはarrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )、それを達成するために使用することについて言及すること、そしてIFS=$'\n'将来ここに上陸してスペースを含む文字列を分割する必要がある人々のためにIFSを変更するためのアドバイスを逃したと思います。(そしてそれを後で復元するために)。:)
Luca Borrione

1
@ルカ良い点。しかし、配列の割り当ては、最初の質問に回答を書いたときはありませんでした。
ローター

65

これも機能します:

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

注意してください、このソリューションは常に正しいとは限りません。「bla@some.com」のみを渡す場合、ADD1とADD2の両方に割り当てられます。


1
あなたは上記の問題を回避するには、-sを使用することができます。superuser.com/questions/896800/... 「-f、--fields = LISTは、これらのフィールドを選択します。また、何の区切り文字を含まない任意の行を印刷し、-sオプションがある場合を除き指定された」
fersarr 2016

34

ダロンの答えの別の見方、これは私がそれをする方法です:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

そうだと思います!上記のコマンドを実行してから「echo $ ADDR1 ... $ ADDR2」を実行すると、「bla@some.com ... john@home.com」という出力が得られます
nickjb

1
これは本当にうまくいきました... mysqldumpを使用するために、カンマで区切られたDB、SERVER、PORTデータを含む文字列の配列を上書きするために使用しました。
Nick

5
診断:IFS=";"割り当ては$(...; echo $IN)サブシェルにのみ存在します。これが、一部の読者(私を含む)が最初は機能しないと考えている理由です。私はすべての$ INがADDR1によって丸められていると思いました。しかし、nickjbは正しいです。それは機能します。その理由は、echo $INコマンドは$ IFSの現在の値を使用して引数を解析しますが、$ IFSの設定に関係なく、スペース区切り文字を使用してそれらをstdoutにエコーします。したがって、正味の効果は1つが呼び出されたかのようになりますread ADDR1 ADDR2 <<< "bla@some.com john@home.com"(入力は;-ではなくスペースで区切られていることに注意してください)。
dubiousjim

1
これはスペースと改行で失敗し、引用符で囲まれていない変数展開でワイルドカード*echo $IN展開します。
Isaac、

私はこのソリューションが本当に好きです。それが機能する理由の説明は非常に有用であり、全体的な答えを改善します。
Michael Gaskill 2017年

32

弾丸を証明する方法であるBashでは、変数に改行が含まれていても機能します。

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

見て:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

これが機能するための秘訣は-dread(区切り文字)のオプションを空の区切り文字と共に使用することreadです。これにより、供給されたすべての文字を強制的に読み取らせます。そしてread、変数の内容を正確にフィードし、のinおかげで末尾の改行はありませんprintfprintf渡された文字列にread末尾の区切り文字があることを確認するために、区切り文字も挿入していることに注意してください。それreadがなければ、潜在的な後続の空のフィールドをトリミングします:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

末尾の空のフィールドは保持されます。


Bash≥4.4の更新

Bash 4.4以降、組み込みmapfile(別名readarray)は-d区切り文字を指定するオプションをサポートしています。したがって、別の標準的な方法は次のとおりです。

mapfile -d ';' -t array < <(printf '%s;' "$in")

5
私はそれが\n、スペース、および*同時に正しく機能するそのリストのまれな解決策であることを発見しました。また、ループはありません。配列変数は、実行後にシェルでアクセスできます(最高の回答とは異なります)。注、in=$'...'二重引用符では機能しません。もっと賛成票が必要だと思います。
John_West 2016年

28

配列を使用していない場合、この1つのライナーはどうでしょうか。

IFS=';' read ADDR1 ADDR2 <<<$IN

を使用read -r ...して、たとえば、入力内の2つの文字「\ t」が変数内の同じ2文字(単一のタブ文字ではなく)になるようにすることを検討してください。
dubiousjim

-1これはここでは機能しません(ubuntu 12.04)。echo "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"スニペットに追加すると出力されますADDR1 bla@some.com john@home.com\nADDR2(\ nは改行)
Luca Borrione

これはおそらく4.3 IFSで修正されたhereとstringに関連するバグが原因bashです。引用$INはそれを修正する必要があります。(理論的に$INは、展開後の単語の分割やグロビングの影響を受けないため、引用符は不要です。ただし4.3でも、少なくとも1つのバグが残っています-報告され、修正される予定です-したがって、引用符は適切ですアイデア。)
chepner、2015

$ INが引用されていても、$ inに改行が含まれていると、これは壊れます。そして、末尾の改行を追加します。
Isaac

これと他の多くのソリューションの問題は、$ INに2つの要素があることを前提としていること、または2番目以降のアイテムをADDR2で一緒に分割することをいとわないことです。これは要求を満たしていると理解していますが、それは時限爆弾です。
Steven the Easily Amused

22

IFSを設定せずに

コロンが1つしかない場合は、次のようにできます。

a="foo:bar"
b=${a%:*}
c=${a##*:}

あなたは得るでしょう:

b = foo
c = bar

20

ここにきれいな3ライナーがあります:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

ここIFSで、セパレータに基づいて単語を区切り()配列の作成に使用されます。次に[@]、各項目を個別の単語として返すために使用されます。

あなたはその後任意のコードをした場合は、復元する必要がある$IFS、例えばunset IFS


5
$inunquotedを使用すると、ワイルドカードを拡張できます。
Isaac

10

次のBash / zsh関数は、2番目の引数で指定された区切り文字で最初の引数を分割します。

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

たとえば、コマンド

$ split 'a;b;c' ';'

収量

a
b
c

この出力は、たとえば、他のコマンドにパイプ処理できます。例:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

与えられた他のソリューションと比較して、これには次の利点があります。

  • IFSはオーバーライドされません:ローカル変数の動的スコープでもIFS、ループをオーバーライドすると、ループ内から実行された関数呼び出しに新しい値がリークします。

  • 配列は使用されません。文字列を配列に読み込むには、Bashとzshでreadフラグ-aが必要-Aです。

必要に応じて、関数を次のようにスクリプトに含めることができます。

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"

1文字より長い区切り文字では機能しないようです:split = $(split "$ content" "file://")
madprops

True-からhelp read-d delim continue until the first character of DELIM is read, rather than newline
Halle Knast

8

あなたは多くの状況にawkを適用できます

echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

これも使えます

echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"

7

このようなシンプルでスマートな方法があります:

echo "add:sfff" | xargs -d: -i  echo {}

ただし、gnu xargsを使用する必要があります。BSDxargsは-d delimをサポートできません。私のようにアップルマックを使えば。gnu xargsをインストールできます:

brew install findutils

その後

echo "add:sfff" | gxargs -d: -i  echo {}

4

これが最も簡単な方法です。

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

4

ここにはいくつかのクールな答えがあります(エラーエスピーター)。

IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";

さて${a[0]}${a[1]}あなたが期待するように、など、です。${#a[*]}用語の数に使用します。またはもちろん、繰り返します:

for i in ${a[*]}; do echo $i; done

重要な注意点:

これは、心配するスペースがない場合に機能し、私の問題は解決しましたが、あなたの問題は解決しない可能性があります。$IFSその場合は、ソリューションを使用してください。


3 INつ以上の電子メールアドレスが含まれている場合は機能しません。palindromの回答
olibre 2013年

${IN//;/ }(2つのスラッシュ)を使用して、2つ以上の値でも機能するようにしてください。ワイルドカード(*?[)は展開されることに注意してください。そして、末尾の空のフィールドは破棄されます。
Isaac

3
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

出力

bla@some.com
john@home.com

システム:Ubuntu 12.04.1


IFSはreadここの特定のコンテキストで設定されていないため、残りのコードがあればそれを混乱させる可能性があります。
codeforester 2017年

2

スペースがない場合、これはなぜですか?

IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

2

set組み込みを使用して$@配列をロードします。

IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'

次に、パーティーを始めましょう:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2

set -- $INダッシュで始まる「$ IN」に関するいくつかの問題を回避するためのより良い使用法。それでも、引用符で囲まれていない展開で$INはワイルドカード(*?[)が展開されます。
Isaac

2

どちらもbash配列を必要としない2つのボーンっぽい選択肢:

ケース1:シンプルかつシンプルに保つ:レコード区切り文字としてNewLineを使用します...例

IN="bla@some.com
john@home.com"

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

注:この最初のケースでは、リスト操作を支援するサブプロセスはフォークされません。

アイデア:多分それは内部で広範囲にNLを使用する価値があり、外部で最終結果を生成するときにのみ別のRSに変換するます。

ケース2:「;」を使用する レコードセパレータとして...例

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

どちらの場合も、ループ内でサブリストを構成できますが、ループが完了した後も永続的です。これは、リストをファイルに保存する代わりに、メモリ内のリストを操作するときに役立ちます。{ps落ち着いてB-)を続ける}


2

既に提供されている素晴らしい答えは別として、使用を検討するデータを印刷するだけの場合は、次のようにしますawk

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

これにより、フィールドセパレーターがに設定さ;れ、forループでフィールドをループして、それに応じて印刷できるようになります。

テスト

$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]

別の入力で:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

2

Androidシェルでは、提案されているメソッドのほとんどが機能しません。

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

何が機能します:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

where //はグローバル置換を意味します。


1
$ PATHの一部にスペース(または改行)が含まれていると失敗します。ワイルドカード(アスタリスク*、疑問符?、中括弧[…])も展開します。
Isaac

2
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

出力:

bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

説明:括弧()を使用した単純な割り当ては、セミコロンで区切られたリストを配列に変換します。標準のFORループは、その配列の個々の項目を通常どおり処理します。IN変数に指定されたリストは「ハード」に引用符で囲まれている必要があることに注意してください。

Bashは割り当てをコマンドと同じ方法で処理しないため、IFSを保存して復元する必要があります。別の回避策は、割り当てを関数内にラップし、変更されたIFSでその関数を呼び出すことです。その場合、IFSの個別の保存/復元は必要ありません。それを指摘してくれた「Bize」に感謝します。


!"#$%&/()[]{}*? are no problem[]*?ええと...完全ではない:グロブ文字です。では、このディレクトリとファイルの作成についてはどうですか: `mkdir '!"#$%&'; touch '! "#$%&/()[] {}は、はははは-問題ありません'とコマンドを実行していますか?simpleは美しいかもしれませんが、壊れると壊れます。
gniourf_gniourf 2015

@gniourf_gniourf文字列は変数に格納されます。元の質問をご覧ください。
ajaaskel 2015

1
@ajaaskelあなたは私のコメントを完全に理解していませんでした。スクラッチディレクトリに移動し、次のコマンドを発行しますmkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'。彼らはディレクトリとファイルを作成するだけで、奇妙に見える名前で、私は認めなければなりません。次に、指定したINとおりにコマンドを実行しますIN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'。期待どおりの出力が得られないことがわかります。パス名展開の対象となるメソッドを使用して文字列を分割しているためです。
gniourf_gniourf 2015

これは、文字ことを実証することである*?[...]および場合でも、extglob設定されている、!(...)@(...)?(...)+(...) です。この方法の問題!
gniourf_gniourf 2015

1
@gniourf_gniourfグロビングに関する詳細なコメントをありがとう。グロビングがオフになるようにコードを調整しました。しかし、私の目的は、かなり単純な割り当てが分割作業を実行できることを示すことだけでした。
ajaaskel 2015

1

大丈夫!

これが私の答えです!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

なぜこのアプローチが「最高」なのですか?

2つの理由により:

  1. 区切り文字をエスケープする必要はありませ
  2. 空白問題ありません。値は配列で適切に区切られます!

[]の


FYI、/etc/os-releaseおよび/etc/lsb-release調達、および解析されないことを意味しています。だからあなたの方法は本当に間違っています。さらに、デリミタで文字列を分割する
gniourf_gniourf 2017年

0

';'で区切られた文字列を分割するワンライナー 配列に:

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

これはIFSをサブシェルに設定するだけなので、その値の保存と復元について心配する必要はありません。


-1これはここでは機能しません(ubuntu 12.04)。すべての$ IN値を含む最初のエコーのみを出力し、2番目のエコーは空です。echo "0:" $ {ADDRS [0]} \ n echo "1:" $ {ADDRS [1]}と入力すると表示されます0: bla@some.com;john@home.com\n 1:(\ nは改行です)
Luca Borrione

1
このアイデアの
有効な

1
-1、1.そのサブシェルでIFSが設定されていません(組み込みである "echo"の環境に渡されているため、何も起こりません)。2. $INは引用されているため、IFS分割の対象にはなりません。3.プロセスの置換は空白で分割されますが、元のデータが破損する可能性があります。
Score_Under

0

多分最もエレガントな解決策ではありませんが、*スペースで動作します:

IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

アウトプット

> [bla@so me.com]
> [*]
> [john@home.com]

その他の例(最初と最後の区切り文字):

IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []

基本的にそれは;作る以外のすべての文字を削除しdelimsます。;;;。次に、によってカウントされるようにto forからループします。最後のステップは、を使用してth部分を安全に取得することです。1number-of-delimiters${#delims}$icut

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