web-dev-qa-db-ja.com

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

この文字列を変数に格納します。

IN="[email protected];[email protected]"

では、文字列を;デリミタで分割して、次のようにします。

ADDR1="[email protected]"
ADDR2="[email protected]"

私は必ずしもADDR1およびADDR2変数を必要としません。それらが配列の要素であれば、それはさらに優れています。


以下の答えからの提案の後で、私は私が後にいたものである以下に終わりました:

#!/usr/bin/env bash

IN="[email protected];[email protected]"

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

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

出力:

> [[email protected]]
> [[email protected]]

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

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

IN="[email protected];[email protected]"

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

IFS=$OIFS

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

mails2=($IN)

$INを囲む括弧なしで、ループでそれを印刷するとき、私は最初の文字列だけを手に入れました。

1733
stefanB

内部フィールド区切り文字 (IFS)変数を設定し、それを解析して配列にすることができます。これがコマンド内で発生すると、IFSへの代入はその単一コマンドの環境(readへ)にのみ行われます。それはそれからIFS変数値に従って入力を配列にパースします、そしてそれを繰り返すことができます。

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

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

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

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

IN="[email protected];[email protected]"
arrIN=(${IN//;/ })

説明:

この構造は、文字列IN内のすべての';'(最初の//はグローバル置換を意味します)を' '(単一のスペース)に置き換えてから、スペースで区切られたストリングを配列として解釈します(周囲の括弧も同様です)。

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

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

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

あなたがすぐにそれらを処理することを気にしないならば、私はこれをするのが好きです:

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

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

220
Chris Lutz

互換性のある答え

このSO質問に対して、これを行うには bash の中ですでにさまざまな方法があります。しかしbashにはspecialという多くの機能があり、いわゆるbashismはうまく機能しますが、他の Shell では機能しません。

特に、配列連想配列、およびパターン置換は純粋なバッシュであり、他のシェルの下では動作しない可能性があります。 _。

私のDebian GNU/Linuxには、 dash と呼ばれる標準シェルがありますが、私は ksh を使うことを好む多くの人々を知っています。

最後に、ごく小さな状況では、彼自身のシェルインタプリタ( ash )を持つ busybox と呼ばれる特別なツールがあります。

要求された文字列

SO questionの文字列サンプルは次のとおりです。

IN="[email protected];[email protected]"

これは空白文字と一緒に使用すると便利であり、空白文字はルーチンの結果を変更する可能性があるので、このサンプル文字列を使用することをお勧めします。

 IN="[email protected];[email protected];Full Name <[email protected]>"

bash 内の区切り文字に基づいて文字列を分割する(version> = 4.2)

pure bashでは、配列IFSを使うことができます。

var="[email protected];[email protected];Full Name <[email protected]>"
oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS
IFS=\; read -a fields <<<"$IN"

最近のbashでこの構文を使用しても、現在のセッションの$IFSは変更されず、現在のコマンドに対してのみ変更されます。

set | grep ^IFS=
IFS=$' \t\n'

文字列varは分割され、配列(fields)に格納されます。

set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'

declare -pで可変コンテンツをリクエストすることができます:

declare -p IN fields
declare -- IN="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")

フォークや外部リソースが呼び出されないため、readは分割を行うためのquickiest方法です。

そこから、あなたは各フィールドを処理するためにあなたがすでに知っているシンタックスを使うことができます:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

または処理後に各フィールドを削除します(私はこのShiftのアプローチが好きです):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

あるいは単純な印刷(短い構文)の場合でも:

printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

更新日:最近の bash > = 4.4

あなたはmapfileで遊ぶことができます:

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

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

空のフィールドを気にしないのであれば、

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

しかし、あなたは関数を通してフィールドを使うことができます:

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 '[email protected]', done.
Seq:      1: Sending mail to '[email protected]', done.
Seq:      2: Sending mail to 'Full Name <[email protected]>', done.

または <<< bash構文で追加された改行を関数にドロップします。

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

同じ出力をレンダリングします。

Seq:      0: Sending mail to '[email protected]', done.
Seq:      1: Sending mail to '[email protected]', done.
Seq:      2: Sending mail to 'Full Name <[email protected]>', done.

Shell 内の区切り文字に基づいて文字列を分割する

しかし、たくさんのシェルで使えるものを書くのであれば、notuse bashismsとする必要があります。

サブストリングのfirstまたはlastの出現箇所にまたがってストリングを分割するための、多くのシェルで使用される構文があります。

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(これが欠けているのが私の回答出版の主な理由です。)

Score_Under で指摘されているように、

#%は、一致する可能性のある最短の文字列を削除します。

##%%は、最も長いものを削除します。

###は、文字列の左から(開始)を意味します。

%%% meand 文字列の右から(末尾)。

この小さなサンプルスクリプトは bashdashkshbusybox でうまく機能し、Mac-OSのbashでもテストされています。

var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

楽しむ!

159
F. Hauri

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

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

例:

$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]

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

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

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

cutはこのファイルをcatすることができ、さらなる処理のために特定のフィールドを選択するのに非常に便利です。

124
DougW

これは私のために働いた:

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

このアプローチはどうでしょうか。

IN="[email protected];[email protected]" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

出典

83
errator
62
lothar

これも動作します:

IN="[email protected];[email protected]"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

注意してください、この解決策は常に正しいとは限りません。あなたが "[email protected]"だけを渡す場合、それはADD1とADD2の両方にそれを割り当てます。

61
Ashok

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

echo "[email protected];[email protected]" | awk -F';' '{print $1,$2}'

あげる

[email protected] [email protected]

もちろん、あなたはawkのprintフィールドを再定義することによって各Eメールアドレスを保存することができます。

38
Tony

Darronの答え 、これは私のやり方です。

IN="[email protected];[email protected]"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
30
nickjb

強固な方法である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")'

これを機能させるためのトリックは、空の区切り文字を持つread(delimiter)の-dオプションを使用することです。そのため、readは、入力されたものすべてを強制的に読み取るようになります。そしてreadのおかげで、最後の改行なしで、inに変数printfの内容を正確に入れます。 printfに渡される文字列が末尾の区切り文字を持つようにするために、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")
26
gniourf_gniourf

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

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

これはきれいな3ライナーです:

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

ここでIFSは区切り文字に基づいて単語を区切り、()array を作成するために使用されます。その後、[@]を使用して各項目を個別のWordとして返します。

それ以降にコードがある場合は、$IFSも復元する必要があります。 unset IFS

19
kenorb

IFSを設定せずに

コロンが1つしかない場合は、それを実行できます。

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

あなたが得るでしょう:

b = foo
c = bar
16
Emilien Brigand

次の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

与えられた他の解決策と比較して、これには以下の利点があります。

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

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

必要に応じて、以下のように関数をスクリプトに入れることができます。

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"
8
Halle Knast

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

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

しかし、あなたはgnu xargsを使わなければなりません、BSD xargsは-d delimをサポートできません。あなたが私のようにアップルのMacを使っているなら。 gnu xargsをインストールすることができます。

brew install findutils

それから

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

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

echo "[email protected];[email protected]"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

これも使えます

echo "[email protected];[email protected]"|awk -F';' '{print $1,$2}' OFS="\n"
6
shuaihanhungry

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

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

ここではいくつかの素晴らしい答えがありますが(他の言語で分割するのと似たようなもの - これは私が最初の質問で言ったことです) - 私はこれを解決しました。

IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";

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

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

重要な注意点:

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

4
eukras
IN="[email protected];[email protected]"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

出力

[email protected]
[email protected]

システム:Ubuntu 12.04.1

3
rashok

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

ケース1 :簡潔に保つ:レコード区切り文字としてNewLineを使用してください。

IN="[email protected]
[email protected]"

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

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

考え:多分それはNLを広範囲に internal を使って、そして最終結果を生成するときだけ別のRSに変換する価値があります external .

ケース2 : ";"を使用レコードセパレータとして...例えば。

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

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

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

IN="[email protected];[email protected]"
IN="$(conv_IRS ";" <<< "$IN")"

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

どちらの場合も、サブリストはループ内で構成でき、ループが完了した後も持続します。これは、リストをファイルに保存する代わりにメモリ内のリストを操作するときに便利です。 {p.s.落ち着いて、B-)を続けてください。

2
NevilleDNZ

スペースがないのなら、どうしてこれじゃないの?

IN="[email protected];[email protected]"
arr=(`echo $IN | tr ';' ' '`)

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

すでに提供されている素晴らしい答えは別として、それが単にデータを印刷することの問題であるならば、あなたはawkを使うことを考慮するかもしれません:

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

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

テスト

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

別の入力で:

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

大丈夫みんな!

これが私の答えです!

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. あなたは 空白スペースの問題を抱えないでしょう 。値は配列内で正しく区切られます。

[]

1
Eduardo Lucio
IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? 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

出力:

[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

説明:括弧()を使用した単純代入は、セミコロンで区切られたリストを配列に変換します。標準のFORループは、通常通りその配列内の個々の項目を処理します。 IN変数に与えられたリストは "ハード"クォートでなければならない、つまりシングルティックでなければならないことに注意してください。

Bashは代入をコマンドと同じようには扱わないため、IFSを保存して復元する必要があります。別の回避策は、関数内で割り当てをラップし、修正されたIFSを使用してその関数を呼び出すことです。その場合、IFSを個別に保存/復元する必要はありません。それを指摘してくれて "Bize"をありがとう。

1
ajaaskel

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

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

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

IN="[email protected];[email protected]"
IFS=';'; set $IN; IFS=$' \t\n'

その後、パーティーを始めましょう:

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

最も洗練された解決策ではないかもしれませんが、*とスペースで動作します。

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

アウトプット

> [bla@so me.com]
> [*]
> [[email protected]]

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

IN=";bla@so me.com;*;[email protected];"
> []
> [bla@so me.com]
> [*]
> [[email protected]]
> []

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

0
Petr Újezdský

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

IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

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

0
Michael Hale

これは空白文字も扱います。

IFS=';' read ADDR1 ADDR2 <<< $(echo ${IN})
0
Mat Bess