文字列をBashの配列に分割する


641

Bashスクリプトで、行を分割して配列に格納したいと思います。

この線:

Paris, France, Europe

私はそれらをこのような配列にしたいと思います:

array[0] = Paris
array[1] = France
array[2] = Europe

簡単なコードを使いたいのですが、コマンドの速度は関係ありません。どうすればできますか?


22
これはGoogleの1位のヒットですが、残念ながら質問では, (カンマスペース)の区切りについての質問であり、カンマなどの単一の文字についての質問ではないためです。あなたは後者のみに興味があるなら、答えはここに従うことが容易です:stackoverflow.com/questions/918886/...
antak

文字列を変更したいが、それを配列として保持する必要がない場合cutは、心に留めておくと便利なbashコマンドです。セパレータは定義可能en.wikibooks.org/wiki/Cut固定幅のレコード構造からデータを抽出することもできます。en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htm
JGFMK

回答:


1090
IFS=', ' read -r -a array <<< "$string"

の文字は$IFS個別にセパレータとして扱われるため、この場合、フィールドは次のいずれかで区切ることができますカンマまたはスペースではなく2つの文字のシーケンス。興味深いことに、スペースが特別に扱われるため、入力にカンマスペースが表示される場合、空のフィールドは作成されません。

個々の要素にアクセスするには:

echo "${array[0]}"

要素を反復するには:

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

インデックスと値の両方を取得するには:

for index in "${!array[@]}"
do
    echo "$index ${array[index]}"
done

最後の例は、Bash配列がスパースであるため便利です。つまり、要素を削除したり要素を追加したりすると、インデックスが連続しなくなります。

unset "array[1]"
array[42]=Earth

配列の要素数を取得するには:

echo "${#array[@]}"

上記のように、配列はスパースである可能性があるため、長さを使用して最後の要素を取得しないでください。Bash 4.2以降でできることは次のとおりです。

echo "${array[-1]}"

Bashのどのバージョンでも(2.05b以降のどこかから):

echo "${array[@]: -1:1}"

負のオフセットが大きいほど、配列の最後から遠くを選択します。古いフォームのマイナス記号の前のスペースに注意してください。必須です。


15
を使用するだけでIFS=', '、スペースを個別に削除する必要がなくなります。テスト:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
l0b0

4
@ l0b0:ありがとう。何を考えていたのかわかりません。declare -p arrayちなみに、私はテスト出力に使用するのが好きです。
追って通知があるまで一時停止。

1
これは引用を尊重していないようです。たとえば、France, Europe, "Congo, The Democratic Republic of the"これはコンゴの後で分割されます。
Yisrael Dov 2014

2
@YisraelDov:BashだけでCSVを処理する方法はありません。引用符内のカンマと引用符外のカンマの違いはわかりません。高水準言語のlibなどのCSVを理解するツール(Pythonのcsvモジュールなど)を使用する必要があります。
追って通知があるまで一時停止。

5
str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"array=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")メモとして分割されます。したがって、これはスペースのないフィールドでのみ機能します。これIFS=', 'は、が個々の文字のセットであり、文字列の区切り文字ではないためです。
dawg 2017年

333

この質問に対する答えはすべて、何らかの形で間違っています。


間違った答え#1

IFS=', ' read -r -a array <<< "$string"

1:これはの誤用です$IFS。値$IFS変数がされていないとした単一の可変長むしろそれは次のように取得された文字列の区切り、セット単一文字各フィールドの文字列の区切り、read入力ラインから離脱により終了することができる任意のセット内の文字(この例では、コンマまたはスペース)。

実際、世の中の本当のステッカーにとって、の完全な意味$IFSは少し複雑です。bashマニュアルから:

シェルはIFSの各文字を区切り文字として扱い、これらの文字をフィールドターミネーターとして使用して、他の展開の結果を単語に分割します。IFSが設定されていない場合、またはその値がデフォルトの<space> <tab> <newline>である場合、以前の展開の結果の最初と最後の<space><tab>、および<newline>のシーケンスは無視され、先頭または末尾にないIFS文字のシーケンスは単語を区切るのに役立ちます。場合はIFSがデフォルト以外の値を持っているし、空白文字のシーケンス<スペース><タブ>、および<空白文字がIFSIFS空白文字)の値にある限り、単語の最初と最後では無視されます。内の任意の文字IFSされていないIFS任意の隣接するとともに、空白をIFS空白文字は、フィールドを区切ります。IFSの空白文字のシーケンスも区切り文字として扱われます。IFSの値がnullの場合、単語分割は発生しません。

基本的に、デフォルト以外のnull以外の値の$IFS場合、フィールドは(1)すべてが「IFS空白文字」のセットからの1つ以上の文字のシーケンス(つまり、<space><tab><newline>( "newline"は改行(LF)を意味します)はどこにも存在します$IFS)の、または(2)に存在する非 "IFS空白文字"$IFS周囲の「IFS空白文字」の入力行。

OPについては、前の段落で説明した2番目の分離モードが入力文字列に必要なものである可能性がありますが、最初に説明した最初の分離モードがまったく正しくないことは確かです。たとえば、彼の入力文字列がどうなったとしたら'Los Angeles, United States, North America'

IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")

2:このソリューションを1文字の区切り文字(コンマ自体など、つまり後続のスペースや他の手荷物なし)で使用する場合でも、$string変数の値にLFが含まれているreadと、最初のLFに遭遇したら処理を停止します。read組み込みは、呼び出しごとに1行を処理します。この例ではhere-stringメカニズムを使用しているため、入力をステートメントにのみパイプまたはリダイレクトしている場合でもこれは当てはまり、未処理の入力は確実に失われます。を強化するコードreadread組み込みは、それを含むコマンド構造内のデータフローをていません。

これが問題を引き起こす可能性は低いと主張することもできますが、それでも、可能であれば回避すべき微妙な危険です。これは、read組み込み関数が実際に2つのレベルの入力分割を行うという事実によって引き起こされます。最初に行に、次にフィールドに分割されます。OPは1レベルの分割しか必要readとしないため、組み込みのこの使用法は適切ではなく、使用を避ける必要があります。

3:このソリューションの明らかではない潜在的な問題は、read空のフィールドを保持している場合でも、空の場合は常に後続のフィールドを削除することです。ここにデモがあります:

string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")

OPはこれを気にしないかもしれませんが、それでも知っておく価値のある制限です。ソリューションの堅牢性と一般性が低下します。

この問題はread、後で説明するように、入力文字列に入力する直前にダミーの末尾の区切り文字を追加することで解決できます。


不正解#2

string="1:2:3:4:5"
set -f                     # avoid globbing (expansion of *).
array=(${string//:/ })

同様のアイデア:

t="one,two,three"
a=($(echo $t | tr ',' "\n"))

(注:欠落している括弧を、応答者が省略したように見えるコマンド置換の周りに追加しました。)

同様のアイデア:

string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)

これらのソリューションは、配列割り当ての単語分割を利用して、文字列をフィールドに分割します。おかしなことに、同様にread、一般的な単語分割でも$IFS特殊変数が使用されますが、この場合は、デフォルト値の<space> <tab> <newline>に設定されていることを意味しています。に設定されているため、1つ以上のIFSのシーケンス文字(現在はすべて空白文字)はフィールド区切り文字と見なされます。

これは、によってコミットされる2レベルの分割の問題を解決します。readワード分割自体は1レベルの分割のみを構成するためです。ただし、以前と同様に、ここでの問題は、入力文字列の個々のフィールドに既に文字が含まれて$IFSいる可能性があるため、単語分割操作中に不適切に分割されることです。これは、これらの回答者が提供するサンプル入力文字列には当てはまりません(どれほど便利か...)が、もちろん、このイディオムを使用したコードベースが次のリスクを冒すという事実は変わりません。この仮定が将来のある時点で違反された場合、爆破します。もう一度、私の反例を検討してください'Los Angeles, United States, North America'(または'Los Angeles:United States:North America')。

また、単語分割が正常に続いてファイル名の拡張別名パス名展開別名、行われている場合、文字を含む潜在的に破損した単語があろうグロブ)*?または[続いて](及び場合、extglob設定されている、括弧フラグメントにより前?*+@、または!)それらをファイルシステムオブジェクトと照合し、それに応じて単語(「グロブ」)を拡張します。これら3人の回答者の1人目は、set -f事前にしてグロビングを無効にする抑えています。技術的にこれは機能します(ただし、おそらく追加する必要があります)set +f その後、それに依存する可能性のある後続のコードでグロビングを再度有効にします)。ただし、ローカルコードで基本的な文字列から配列への解析操作をハッキングするために、グローバルシェル設定を変更する必要はありません。

この回答の別の問題は、すべての空のフィールドが失われることです。これは、アプリケーションに応じて、問題となる場合とされない場合があります。

注:このソリューションを使用する場合は、コマンド置換(シェルをフォークする)を呼び出してパイプラインを起動するよりも、${string//:/ }「パターン置換」形式のパラメーター展開を使用することをお勧めします。パラメータの拡張は純粋にシェル内部の操作であるため、外部の実行可能ファイル(trまたはsed)を実行します。(また、trおよびsedソリューションの場合、入力変数はコマンド置換内で二重引用符で囲む必要があります。そうしないと、単語分割がechoコマンドで有効になり、フィールド値が混乱する可能性があります。また、$(...)コマンド置換形式が古い形式よりも望ましいです`...` コマンド置換のネストを簡略化し、テキストエディタによる構文の強調表示を改善するためです。


不正解#3

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

この答えは#2とほとんど同じです。違いは、回答者はフィールドが2つの文字で区切られていると想定していることです$IFS。彼は、パターン置換拡張を使用して非IFS表現の文字を削除し、単語分割を使用して、残っているIFS表現の区切り文字のフィールドを分割することにより、このかなり具体的なケースを解決しました。

これは非常に一般的なソリューションではありません。さらに、ここではコンマは実際には「プライマリ」区切り文字であり、フィールドを分割するためにコンマを削除してスペース文字に依存することは単に間違っていると主張できます。もう一度、私の反例を考えてみましょう'Los Angeles, United States, North America'

また、ファイル名を拡張すると、拡張された単語が破損する可能性がありますが、これは、set -fおよびによる割り当てのグロビングを一時的に無効にすることで防ぐことができますset +f

また、空のフィールドはすべて失われますが、アプリケーションによっては問題となる場合とそうでない場合があります。


不正解#4

string='first line
second line
third line'

oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"

これは、単語分割を使用してジョブを実行するという点で#2#3に似ていますが、コード$IFSは入力文字列に存在する1文字のフィールド区切り文字のみを含むように明示的に設定しているだけです。これは、OPのコンマスペース区切り文字などの複数文字のフィールド区切り文字では機能しないことを繰り返します。しかし、この例で使用されているLFのような1文字の区切り文字の場合、実際には完全に近いものになります。以前の間違った回答で見たように、フィールドを途中で意図せずに分割することはできません。必要に応じて、分割のレベルは1つだけです。

1つの問題は、前述のようにファイル名の展開によって影響を受ける単語が破損することですが、重要なステートメントをset -fとでラップすることでこれを解決できますset +f

別の潜在的な問題は、前に定義したようにLFが「IFS空白文字」として修飾されるため、#2および#3と同様に、すべての空のフィールドが失われることです。もちろん、デリミタが「IFS空白文字」以外の場合は問題になりません。アプリケーションによっては、問題にならない場合もありますが、ソリューションの一般性は損なわれます。

つまり、1文字の区切り文字があり、それが非「IFS空白文字」であるか、空のフィールドを気にしないで、重要なステートメントをset -fand set +fでラップすると、このソリューションは機能します。 、ただしそれ以外の場合。

(また、参考のために、bashの変数へのLFの割り当ては、$'...'構文などを使用するとより簡単に実行できますIFS=$'\n';。)


不正解#5

countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"

同様のアイデア:

IFS=', ' eval 'array=($string)'

このソリューションは、実質的に#1$IFSカンマスペースに設定される)と#2-4(単語分割を使用して文字列をフィールドに分割する)の間のクロスです。このため、上記の間違った答えのすべてに影響するほとんどすべての問題に苦しんでいます。

また、2番目のバリアントに関してはeval、その引数は単一引用符で囲まれた文字列リテラルであるため、呼び出しは完全に不要であるように見える可能性があり、静的に認識されます。しかしeval、この方法で使用することには、実際には非常に明白でない利点があります。通常、変数の割り当てのみで構成される単純なコマンドを実行すると、実際のコマンドワードが後に続かないため、シェル環境で割り当てが有効になります。

IFS=', '; ## changes $IFS in the shell environment

これは、単純なコマンドに複数の変数割り当てが含まれる場合でも当てはまります。繰り返しになりますが、コマンドワードがない限り、すべての変数の割り当てがシェル環境に影響します。

IFS=', ' array=($countries); ## changes both $IFS and $array in the shell environment

ただし、変数の割り当てがコマンド名に付加されている場合(これを「プレフィックス割り当て」と呼びます)、シェル環境に影響せ、組み込みかどうかに関係なく、実行されたコマンドの環境にのみ影響します。または外部:

IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it

bashマニュアルからの関連引用:

コマンド名が表示されない場合、変数の割り当ては現在のシェル環境に影響します。それ以外の場合、変数は実行されたコマンドの環境に追加され、現在のシェル環境には影響しません。

変数割り当てのこの機能を利用して$IFS一時的にのみ変更することができます。これにより$OIFS、最初のバリアントで変数を使用して行われているような保存と復元の全体を回避できます。しかし、ここで直面する課題は、実行する必要があるコマンド自体が単なる変数の割り当てであるため、$IFS割り当てを一時的にするためのコマンドワードが含まれないことです。あなたは自分で考えているかもしれませんが、なぜ、何もしないコマンドワードを次のようなステートメントに追加しないでください。: builtin$IFS割り当てを一時的するために、ですか?これは、$array割り当てを一時的にするため、機能しません。

IFS=', ' array=($countries) :; ## fails; new $array value never escapes the : command

ですから、私たちは事実上行き詰まりに陥っています。しかし、evalそのコードを実行すると、通常の静的ソースコードのようにシェル環境で実行されるため$arrayeval引数内で代入を実行して、シェル環境で有効にすることができます。$IFS、そのプレフィックスの割り当て接頭辞evalコマンドのコマンドよりも長く存続しませんeval。これは、まさにこのソリューションの2番目のバリアントで使用されているトリックです。

IFS=', ' eval 'array=($string)'; ## $IFS does not outlive the eval command, but $array does

ご覧のように、これは実際にはかなり巧妙なトリックであり、(少なくとも割り当ての効果に関して)かなり明白でない方法で、必要なものを正確に実現します。の関与にもかかわらず、私は実際には一般的にこのトリックに反対していませんeval。セキュリティの脅威から保護するために、引数文字列を一重引用符で囲むように注意してください。

しかし、繰り返しになりますが、「すべての世界で最悪の」問題の集合体のため、これは依然としてOPの要件に対する誤った答えです。


不正解#6

IFS=', '; array=(Paris, France, Europe)

IFS=' ';declare -a array=(Paris France Europe)

えっと…何?OPには、配列に解析する必要がある文字列変数があります。この「答え」は、配列リテラルに貼り付けられた入力文字列の逐語的な内容から始まります。それが一つの方法だと思います。

回答者は、 $IFS変数がすべてのコンテキストですべてのbash解析に影響を与えるますが、これは正しくありません。bashマニュアルから:

IFS     拡張後の単語分割に使用され、read組み込みコマンドで行を単語に分割するために使用される内部フィールド区切り文字。デフォルト値は<space> <tab> <newline>です。

したがって、$IFS特殊変数は実際には2つのコンテキストでのみ使用されます:(1)展開後に実行される単語分割(bashソースコードを解析するときではない)、および(2)read組み込みによって入力行を単語に分割するため。

これをもっと明確にしてみましょう。解析実行を区別するのは良いことだと思います。Bashは最初にソースコードを解析する必要があります。これは明らかに解析イベントであり、その後、コードを実行します。このとき、拡張機能が画面に入ります。拡張は実際には実行イベントです。さらに、$IFS上で引用した変数の説明に問題があります。展開後に単語分割が実行されると言うのではなく、単語分割が実行されると言います時に拡大、または、おそらくより正確には、単語の分割があるの一部拡張プロセス。「単語分割」という語句は、この拡張ステップのみを指します。残念ながらドキュメントは「split」や「words」という単語を頻繁に投げているようですが、bashソースコードの解析を参照するために使用することはできません。次に、bashマニュアルのlinux.die.netバージョンからの関連する抜粋を示します。

展開は、単語に分割された後、コマンドラインで実行されます。実行される展開にはブレース展開チルダ展開パラメータと変数展開コマンド置換算術展開単語分割パス名展開の 7種類があります

展開の順序は次のとおりです。チルダ展開、パラメーターおよび変数展開、算術展開、およびコマンド置換(左から右に実行)。単語分割; およびパス名の展開。

あなたはGNUのバージョンを議論することができます展開セクションの最初の文で「words」ではなく「tokens」という単語を選択しているため、マニュアルののほうが少し優れてます。

展開は、トークンに分割された後、コマンドラインで実行されます。

重要な点は、$IFS bashがソースコードを解析する方法を変更しないことです。bashソースコードの解析は、実際には非常に複雑なプロセスであり、コマンドシーケンス、コマンドリスト、パイプライン、パラメーター展開、算術置換、コマンド置換などのシェル文法のさまざまな要素の認識が含まれます。ほとんどの場合、bash解析プロセスは、変数割り当てなどのユーザーレベルのアクションでは変更できません(実際には、このルールにはいくつかの小さな例外があります。たとえば、さまざまなシェル設定を参照してください)compatxx、オンザフライで解析動作の特定の側面を変更できます)。この複雑な解析プロセスから生じる上流の「単語」/「トークン」は、上記のドキュメントの抜粋で分解された「拡張」の一般的なプロセスに従って展開されます。言葉は単にそのプロセスの1つのステップです。単語分割は、前の拡張ステップから吐き出されたテキストにのみ影響します。ソースバイトストリームから解析されたリテラルテキストには影響しません。


不正解#7

string='first line
        second line
        third line'

while read -r line; do lines+=("$line"); done <<<"$string"

これは最良のソリューションの1つです。を再び使用することに注意してくださいreadread1つだけ必要なときに2つのレベルの分割を実行するため、これは不適切だと以前に言っていませんか ここでの秘訣は、read呼び出しごとに1レベルの分割のみを効果的に行うように呼び出すことができるということです。これは、呼び出しごとに1つのフィールドのみを分割することにより、ループ内で繰り返し呼び出す必要があるというコストを必要とします。それは少し手技ですが、うまくいきます。

しかし、問題があります。まず、少なくとも1つのNAME引数をreadに指定すると、入力文字列から分割された各フィールドの先頭と末尾の空白は自動的に無視されます。これは$IFS、この投稿で前述したように、がデフォルト値に設定されているかどうかに関係なく発生します。さて、OPは彼の特定のユースケースについてこれを気にしないかもしれません、そして実際、それは解析動作の望ましい機能かもしれません。しかし、文字列を解析してフィールドにしたいすべての人がこれを望んでいるわけではありません。ただし、解決策があります。のやや目立たない使用法readは、ゼロのNAME引数を渡すことです。この場合、readは、入力ストリームから取得した入力行全体をという名前の変数に格納します。$REPLYおまけとして、ではありません値から先頭と末尾の空白を取り除きます。これは、read私がシェルプログラミングのキャリアで頻繁に利用した非常に堅牢な使用法です。これが動作の違いのデモです。

string=$'  a  b  \n  c  d  \n  e  f  '; ## input string

a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a  b" [1]="c  d" [2]="e  f") ## read trimmed surrounding whitespace

a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="  a  b  " [1]="  c  d  " [2]="  e  f  ") ## no trimming

このソリューションの2番目の問題は、OPのカンマスペースなどのカスタムフィールドセパレーターのケースに実際には対応しないことです。以前と同様に、複数文字のセパレータはサポートされていません。これは、このソリューションの残念な制限です。-dオプションにセパレータを指定することにより、少なくともコンマで分割しようとすることができますが、何が起こるか見てください:

string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")

予想外に、考慮されていない周囲の空白がフィールド値に取り込まれるため、後でトリミング操作によって修正する必要があります(これは、whileループで直接行うこともできます)。しかし、もう1つの明らかなエラーがあります。ヨーロッパがありません!それがどうなったのか?その答えはread、最終フィールドで最終フィールドターミネータに遭遇することなく、ファイルの終わり(この場合は文字列の終わりと呼ぶことができます)にヒットした場合、失敗した戻りコードを返します。これにより、whileループが途中で中断し、最後のフィールドが失われます。

技術的には、この同じエラーが前の例にも影響を与えました。違いは、フィールド区切り文字がLFであると見なされたことです。これは、-dオプションを指定しない場合のデフォルトであり、<<<( "here-string")メカニズムは、それをフィードする直前に文字列にLFを自動的に追加しますコマンドへの入力。したがって、それらのケースでは、意図せずに追加のダミーターミネーターを入力に追加することにより、最終フィールドのドロップの問題を誤って解決しました。このソリューションを「ダミーターミネーター」ソリューションと呼びましょう。here-stringでインスタンス化するときに、自分で入力文字列に対して連結することにより、カスタム区切り文字にダミーターミネーターソリューションを手動で適用できます。

a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")

そこで、問題は解決しました。別の解決策は、(1)readが失敗を返し、(2)$REPLYが空の場合、つまりreadファイルの終わりに到達する前に文字を読み取ることができなかった場合にのみ、whileループを解除することです。デモ:

a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')

このアプローチは、<<<リダイレクト演算子によってhere-stringに自動的に追加される秘密のLFも明らかにします。もちろん、先ほど説明したように明示的なトリミング操作で個別に取り除くこともできますが、手動のダミーターミネーターのアプローチで直接解決できるので、そのまま使用できます。手動のダミーターミネーターソリューションは、これら2つの問題(ドロップされたファイナルフィールドの問題と追加されたLFの問題)の両方を一度に解決するという点で、実際には非常に便利です。

したがって、全体として、これは非常に強力なソリューションです。残りの弱点は、後で説明する複数文字の区切り文字のサポートがないことだけです。


不正解#8

string='first line
        second line
        third line'

readarray -t lines <<<"$string"

(これは実際には#7と同じ投稿からのものです。回答者は同じ投稿で2つのソリューションを提供しました。)

readarray同義語である組み込みmapfileは理想的です。これは、バイトストリームを解析して配列変数に一度に変換する組み込みコマンドです。ループ、条件、置換、その他をいじる必要はありません。また、入力文字列から空白をこっそり取り除きません。また、(-O指定されていない場合)ターゲット配列に割り当てる前に、それを消去します。しかし、それはまだ完璧ではないので、「間違った答え」としてそれを批判します。

まず、これを邪魔にならないようにするためにread、フィールド解析を実行するときの動作と同じようにreadarray、後続のフィールドが空の場合はそれをドロップすることに注意してください。繰り返しますが、これはおそらくOPの問題ではありませんが、一部のユースケースでは問題になる可能性があります。すぐに戻ってきます。

次に、以前と同様に、複数文字の区切り文字はサポートされていません。この問題についても、すぐに修正します。

第3に、記述されたソリューションはOPの入力文字列を解析せず、実際には、そのままでは解析に使用できません。これについても少し詳しく説明します。

上記の理由により、私はこれをOPの質問に対する「間違った答え」であるとまだ考えています。以下に、私が正しい答えであると考えるものを示します。


正しい答え

オプションを指定するだけで#8を機能させる単純な試みを以下に示し-dます。

string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')

結果はread#7で説明したループソリューションの二重条件アプローチから得られた結果と同じであることがわかります。これは手動のダミーターミネータートリックでほぼ解決できます:

readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')

ここでの問題はreadarray<<<リダイレクト演算子が入力文字列にLFを追加したため、後続のフィールドが保持されていたため、後続のフィールドが空ではなかった(そうでない場合は削除された)ことです。事後的に最終的な配列要素の設定を明示的に解除することで、これを処理できます。

readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")

残る2つの問題は、実際には関連していますが、(1)トリムする必要のある余分な空白、および(2)複数文字の区切り文字のサポートの欠如です。

もちろん、空白は後で削除できます(たとえば、Bash変数から空白を削除する方法を参照してください)。しかし、複数文字の区切り文字をハッキングできれば、両方の問題を一度に解決できます。

残念ながら、複数文字の区切り文字を機能させる直接的な方法はありません。私が考えた最善の解決策は、入力文字列を前処理して、複数文字の区切り文字を、入力文字列の内容と衝突しないことが保証される単一文字の区切り文字に置き換えることです。この保証がある唯一の文字はNULバイトです。これは、bashでは(ちなみにzshではありません)、変数にNULバイトを含めることができないためです。この前処理ステップは、プロセス置換のインラインで実行できます。これはawkを使用してそれを行う方法です:

readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")

やっと!この解決策は、フィールドを途中で誤って分割したり、時期尚早に切り取ったり、空のフィールドをドロップしたり、ファイル名の展開でそれ自体が破損したり、先頭と末尾の空白を自動的に削除したり、最後に隠れたLFを残したりしません。ループを必要とせず、単一文字の区切り文字を処理しません。


トリミングソリューション

最後に、のあいまいな-C callbackオプションを使用して、独自のかなり複雑なトリミングソリューションをデモンストレーションしたいと思いましたreadarray。残念ながら、私はStack Overflowの厳格な30,000文字の投稿制限に対して余裕がなくなったので、それを説明することはできません。読者の練習問題として残しておきます。

function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")

8
(当然のことながら、そうする余地はありませんでしたが)Bash 4.4で最初に表示される-dオプションに注意することも役立ちますreadarray
fbicknel 2017

2
正解(+1)。awkをに変更してawk '{ gsub(/,[ ]+|$/,"\0"); print }'、決勝戦の連結を", " 排除した場合、決勝戦を排除するために体操を行う必要はありません。だから:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")をサポートするバッシュでreadarray。あなたの方法はバッシュ4.4以降、私は理由を考えることに注意してください-dreadarray
仲の良い友達

3
@datUser残念です。bashのバージョンがには古すぎる必要がありますreadarray。この場合、で構築された2番目に優れたソリューションを使用できますread。私はこれについて言及しています:(複数文字の区切り文字のサポートが必要な場合a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";awk置換を使用して)。問題が発生した場合はお知らせください。このソリューションは、20年前のようにリリースされたバージョン2の何かに戻って、かなり古いバージョンのbashで動作するはずです。
bgoldst 2018

1
わあ、なんて素晴らしい答えでしょう!喜喜、私の応答:bashスクリプトを破棄し、pythonを起動しました!
artfulrobot

1
OSXの@datUser bashは3.2(2007年頃リリース)のままです。私は、OS X上で4.Xのbashのバージョンを取得するには自作で見つかったのbashを使用しました
JDS

222

IFSを設定しない方法は次のとおりです。

string="1:2:3:4:5"
set -f                      # avoid globbing (expansion of *).
array=(${string//:/ })
for i in "${!array[@]}"
do
    echo "$i=>${array[i]}"
done

アイデアは文字列置換を使用しています:

${string//substring/replacement}

$ substringのすべての一致を空白で置き換え、次に置換された文字列を使用して配列を初期化します。

(element1 element2 ... elementN)

注:この回答では、split + glob演算子を使用しています。したがって、一部の文字(など*)の展開を防ぐには、このスクリプトのグロビングを一時停止することをお勧めします。


1
このアプローチを使用した...分割する長い文字列に出くわすまで。100%CPUが1分以上続いた(それから私はそれを殺した)。この方法では、IFSの一部の文字ではなく文字列で分割できるため、残念です。
Werner Lehmann

1分間よりも100%高いCPU時間は、どこかに問題があるように思えます。その文字列はどれくらいの長さでしたか、それはMBまたはGBサイズですか?通常、小さな文字列の分割が必要なだけならBash内にとどまりたいと思いますが、それが巨大なファイルの場合は、Perlのようなものを実行して実行します。

12
警告:このアプローチで問題が発生しました。*という名前の要素がある場合は、cwdのすべての要素も取得します。したがって、string = "1:2:3:4:*"は、実装によっては、予期しない、場合によっては危険な結果をもたらします。(IFS = '、' read -a array <<< "$ string")で同じエラーが発生せず、これは安全に使用できるようです。
Dieter Gribnitz、2014

4
引用${string//:/ }はシェルの拡張を妨げる
Andrew White

1
私はOSX上で、次を使用していた: array=(${string//:/ })
マーク・トムソン

95
t="one,two,three"
a=($(echo "$t" | tr ',' '\n'))
echo "${a[2]}"

3枚プリント


8
私は実際にこのアプローチを好みます。シンプル。
エビワゴン2015年

4
これをコピーして貼り付けましたが、エコーでは機能しませんでしたが、forループで使用すると機能しました。
ベン、

2
これは述べられたように機能しません。@ Jmoney38またはshrimpwagonこれをターミナルに貼り付けて目的の出力を得ることができる場合は、ここに結果を貼り付けてください。
abalter

2
@abalterはa=($(echo $t | tr ',' "\n"))。と同じ結果 a=($(echo $t | tr ',' ' '))

@procrastinator私はちょうどそれを試してみましたVERSION="16.04.2 LTS (Xenial Xerus)"bash、シェル、最後はechoただの空白行を出力します。Linuxのどのバージョンとどのシェルを使用していますか?残念ながら、コメントにターミナルセッションを表示できません。
abalter 2017

29

特にセパレータがキャリッジリターンである場合、受け入れられた回答で説明されている方法が機能しないことがときどきありました。
それらの場合、私はこのように解決しました:

string='first line
second line
third line'

oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"

for line in "${lines[@]}"
    do
        echo "--> $line"
done

2
+1これは完全に私のために働いた。改行で区切られた複数の文字列を配列に入れる必要があり、では機能read -a arr <<< "$strings"しませんでしたIFS=$'\n'
Stefan van den Akker、2015


これは元の質問に完全に答えるものではありません。
Mike、

29

受け入れられた回答は、1行の値に対して機能します。
変数に複数の行がある場合:

string='first line
        second line
        third line'

すべての行を取得するには、非常に異なるコマンドが必要です。

while read -r line; do lines+=("$line"); done <<<"$string"

または、はるかに単純なbash readarray

readarray -t lines <<<"$string"

すべての行を印刷することは、printf機能を利用して非常に簡単です。

printf ">[%s]\n" "${lines[@]}"

>[first line]
>[        second line]
>[        third line]

2
すべてのソリューションがすべての状況で機能するわけではありませんが、readarrayについての言及は...私の最後の2時間を5分に置き換えました...あなたは私の投票を得ました
Angry 84


6

文字列を配列に分割する鍵は、の複数文字区切り文字です", "IFSIFSは文字列ではなくこれらの文字のセットであるため、複数文字の区切り文字に使用するソリューションは本質的に間違っています。

割り当てたIFS=", "場合、文字列はEITHER ","OR " "またはそれらの任意の組み合わせで中断されますが、これはの2文字の区切り文字の正確な表現ではありません", "

awkまたはsedを使用して、プロセスを置換して文字列を分割できます。

#!/bin/bash

str="Paris, France, Europe"
array=()
while read -r -d $'\0' each; do   # use a NUL terminated field separator 
    array+=("$each")
done < <(printf "%s" "$str" | awk '{ gsub(/,[ ]+|$/,"\0"); print }')
declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output

Bashで直接正規表現を使用する方が効率的です。

#!/bin/bash

str="Paris, France, Europe"

array=()
while [[ $str =~ ([^,]+)(,[ ]+|$) ]]; do
    array+=("${BASH_REMATCH[1]}")   # capture the field
    i=${#BASH_REMATCH}              # length of field + delimiter
    str=${str:i}                    # advance the string by that length
done                                # the loop deletes $str, so make a copy if needed

declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output...

2番目の形式では、サブシェルがなく、本質的に高速になります。


bgoldstによる編集:これは、私のreadarrayソリューションをdawgの正規表現ソリューションと比較するいくつかのベンチマークであり、そのreadソリューションも含めました(注:ソリューションとの調和を高めるために正規表現ソリューションを少し変更しました)(以下のコメントも参照してください)役職):

## competitors
function c_readarray { readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); unset 'a[-1]'; };
function c_read { a=(); local REPLY=''; while read -r -d ''; do a+=("$REPLY"); done < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); };
function c_regex { a=(); local s="$1, "; while [[ $s =~ ([^,]+),\  ]]; do a+=("${BASH_REMATCH[1]}"); s=${s:${#BASH_REMATCH}}; done; };

## helper functions
function rep {
    local -i i=-1;
    for ((i = 0; i<$1; ++i)); do
        printf %s "$2";
    done;
}; ## end rep()

function testAll {
    local funcs=();
    local args=();
    local func='';
    local -i rc=-1;
    while [[ "$1" != ':' ]]; do
        func="$1";
        if [[ ! "$func" =~ ^[_a-zA-Z][_a-zA-Z0-9]*$ ]]; then
            echo "bad function name: $func" >&2;
            return 2;
        fi;
        funcs+=("$func");
        shift;
    done;
    shift;
    args=("$@");
    for func in "${funcs[@]}"; do
        echo -n "$func ";
        { time $func "${args[@]}" >/dev/null 2>&1; } 2>&1| tr '\n' '/';
        rc=${PIPESTATUS[0]}; if [[ $rc -ne 0 ]]; then echo "[$rc]"; else echo; fi;
    done| column -ts/;
}; ## end testAll()

function makeStringToSplit {
    local -i n=$1; ## number of fields
    if [[ $n -lt 0 ]]; then echo "bad field count: $n" >&2; return 2; fi;
    if [[ $n -eq 0 ]]; then
        echo;
    elif [[ $n -eq 1 ]]; then
        echo 'first field';
    elif [[ "$n" -eq 2 ]]; then
        echo 'first field, last field';
    else
        echo "first field, $(rep $[$1-2] 'mid field, ')last field";
    fi;
}; ## end makeStringToSplit()

function testAll_splitIntoArray {
    local -i n=$1; ## number of fields in input string
    local s='';
    echo "===== $n field$(if [[ $n -ne 1 ]]; then echo 's'; fi;) =====";
    s="$(makeStringToSplit "$n")";
    testAll c_readarray c_read c_regex : "$s";
}; ## end testAll_splitIntoArray()

## results
testAll_splitIntoArray 1;
## ===== 1 field =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.000s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 10;
## ===== 10 fields =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.001s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 100;
## ===== 100 fields =====
## c_readarray   real  0m0.069s   user 0m0.000s   sys  0m0.062s
## c_read        real  0m0.065s   user 0m0.000s   sys  0m0.046s
## c_regex       real  0m0.005s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 1000;
## ===== 1000 fields =====
## c_readarray   real  0m0.084s   user 0m0.031s   sys  0m0.077s
## c_read        real  0m0.092s   user 0m0.031s   sys  0m0.046s
## c_regex       real  0m0.125s   user 0m0.125s   sys  0m0.000s
##
testAll_splitIntoArray 10000;
## ===== 10000 fields =====
## c_readarray   real  0m0.209s   user 0m0.093s   sys  0m0.108s
## c_read        real  0m0.333s   user 0m0.234s   sys  0m0.109s
## c_regex       real  0m9.095s   user 0m9.078s   sys  0m0.000s
##
testAll_splitIntoArray 100000;
## ===== 100000 fields =====
## c_readarray   real  0m1.460s   user 0m0.326s   sys  0m1.124s
## c_read        real  0m2.780s   user 0m1.686s   sys  0m1.092s
## c_regex       real  17m38.208s   user 15m16.359s   sys  2m19.375s
##

非常にクールなソリューション!正規表現の一致でループを使用することを考えたことはありませんでした$BASH_REMATCH。それは機能し、サブシェルの生成を実際に回避します。私からの+1。ただし、批判として、正規表現自体は少し非理想的です。つまり、貪欲でない乗数のサポートの欠如を回避するために、区切りトークン(特にコンマ)の一部を複製する必要があったようです。 (これも回避策)ERE(bashに組み込まれた「拡張」正規表現フレーバー)。これにより、汎用性が低くなり、堅牢になります。
bgoldst 2017年

次に、私はいくつかのベンチマークを行いました。パフォーマンスは、小さめの文字列の他のソリューションよりも優れていますが、文字列の再構築が繰り返されるため指数関数的に悪化し、非常に大きな文字列では壊滅的になります。あなたの答えに対する私の編集を見てください。
bgoldst 2017年

@bgoldst:なんてクールなベンチマークでしょう!正規表現を守るために、数千または数十万のフィールド(正規表現が分割しているもの)の場合、おそらく\nこれらのフィールドを構成する何らかの形式のレコード(区切られたテキスト行など)が存在するため、壊滅的なスローダウンは発生しません。100,000フィールドの文字列がある場合-多分Bashは理想的ではありません;-)ベンチマークをありがとう。私は何かを学びました。
dawg 2017年

4

純粋なbashの複数文字区切り記号ソリューション。

他の人がこのスレッドで指摘したように、OPの質問では、配列に解析されるコンマ区切りの文字列の例が示されましたが、コンマ区切り、単一文字区切り、または複数文字のみに関心があるかどうかは示されませんでした区切り文字。

Googleはこの回答を検索結果の上部またはその近くにランク付けする傾向があるため、複数の文字区切り記号の質問に対する強力な回答を読者に提供したいと考えました。これは、少なくとも1つの回答でも言及されているためです。

複数文字の区切り文字の問題の解決策を探している場合は、Malikarjun Mの投稿、特に パラメーター拡張を使用してこのエレガントな純粋なBASHソリューションを提供するgniourf_gniourfからの応答を確認することをお勧めします。

#!/bin/bash
str="LearnABCtoABCSplitABCaABCString"
delimiter=ABC
s=$str$delimiter
array=();
while [[ $s ]]; do
    array+=( "${s%%"$delimiter"*}" );
    s=${s#*"$delimiter"};
done;
declare -p array

引用されたコメント/参照された投稿へのリンク

引用された質問へのリンク:bashで複数文字の区切り文字で文字列を分割する方法?


1
同様ですが改善されたアプローチについては、私のコメントを参照しください。
xebeche

3

これは私にとってOSXで動作します:

string="1 2 3 4 5"
declare -a array=($string)

文字列の区切り文字が異なる場合は、最初の文字をスペースで置き換えます。

string="1,2,3,4,5"
delimiter=","
declare -a array=($(echo $string | tr "$delimiter" " "))

シンプル:-)


プラスであるBashとZshの両方で動作します!
Elijah W. Gagne

2

IFSを変更せずにそれを行う別の方法:

read -r -a myarray <<< "${string//, /$IFS}"

IFSを変更して目的の区切り文字に合わせるのではなく、目的の区切り文字のすべての出現箇所", "$IFSviaの内容に置き換えることができ"${string//, /$IFS}"ます。

多分これは非常に大きな文字列では遅くなるでしょうか?

これは、デニスウィリアムソンの回答に基づいています。


2

次のような入力を解析するときにこの投稿に出くわしました:word1、word2、...

上記のどれも私を助けませんでした。awkを使用して解決しました。それが誰かを助ける場合:

STRING="value1,value2,value3"
array=`echo $STRING | awk -F ',' '{ s = $1; for (i = 2; i <= NF; i++) s = s "\n"$i; print s; }'`
for word in ${array}
do
        echo "This is the word $word"
done

1

これを試して

IFS=', '; array=(Paris, France, Europe)
for item in ${array[@]}; do echo $item; done

それは簡単です。必要に応じて、宣言を追加することもできます(コンマも削除します)。

IFS=' ';declare -a array=(Paris France Europe)

上記を元に戻すためにIFSが追加されましたが、新しいbashインスタンスではそれがなくても機能します


1

trコマンドを使用して、文字列を配列オブジェクトに分割できます。MacOSとLinuxの両方で動作します

  #!/usr/bin/env bash
  currentVersion="1.0.0.140"
  arrayData=($(echo $currentVersion | tr "." "\n"))
  len=${#arrayData[@]}
  for (( i=0; i<=$((len-1)); i++ )); do 
       echo "index $i - value ${arrayData[$i]}"
  done

別のオプションはIFSコマンドを使用します

IFS='.' read -ra arrayData <<< "$currentVersion"
#It is the same as tr
arrayData=($(echo $currentVersion | tr "." "\n"))

#Print the split string
for i in "${arrayData[@]}"
do
    echo $i
done

0

これを使って:

countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"

#${array[1]} == Paris
#${array[2]} == France
#${array[3]} == Europe

3
悪い例:単語の分割とパス名の展開が必要です。悪い答えを与えるために良い答えで古い質問を復活させないでください。
gniourf_gniourf 2016

2
これは悪い答えかもしれませんが、それでも有効な答えです。報告者/レビュアー: このような誤った回答については、投票しないでください。削除しないでください。
Scott Weldon、

2
@gniourf_gniourfなぜそれが悪い答えなのか説明してもらえますか?いつ失敗するのか本当にわかりません。
ジョージ

3
@GeorgeSovetov:私が言ったように、それは単語分割とパス名拡張の対象となります。より一般的には、(悲しいことに非常に一般的な)アンチパターンのように、文字列を配列にarray=( $string )分割しますstring='Prague, Czech Republic, Europe'。単語の分割が発生します。パス名の展開が発生します:などのstring='foo[abcd],bar[efgh]'名前のファイルがある場合、foodまたはbarfディレクトリにある場合は失敗します。そのような構成の唯一の有効な使用法stringは、glob がいつであるかです。
gniourf_gniourf 2016

0

更新:evalの問題のため、これを行わないでください。

少しセレモニーが少ない:

IFS=', ' eval 'array=($string)'

例えば

string="foo, bar,baz"
IFS=', ' eval 'array=($string)'
echo ${array[1]} # -> bar

4
evalは悪です!これを行わないでください。
caesarsol 2015年

1
うん。いいえ。これが問題になるほどの大きさのスクリプトを作成している場合、それは間違っています。アプリケーションコードでは、evalは悪です。シェルスクリプトでは、それは一般的であり、必要であり、重要ではありません。
user1009908 2015年

2
$変数にa を入れると、表示されます...私は多くのスクリプトを記述しており、単一を使用する必要がありませんでしたeval
caesarsol

2
そうです、これは入力がクリーンであることがわかっている場合にのみ使用できます。堅牢なソリューションではありません。
user1009908

私がevalを使用しなければならなかった唯一の時間は、独自のコード/モジュールを自己生成するアプリケーションのためでした...そして、これにはユーザー入力の形式がありませんでした...
Angry 84

0

これが私のハックです!

文字列を文字列で分割することは、bashを使用して行うのはかなり退屈なことです。何が起こるかというと、いくつかのケースでしか機能しないアプローチ(「;」、「/」、「。」などで分割される)が限られている、または出力にさまざまな副作用があるということです。

以下のアプローチにはいくつかの操作が必要ですが、私たちのニーズのほとんどでうまくいくと思います!

#!/bin/bash

# --------------------------------------
# SPLIT FUNCTION
# ----------------

F_SPLIT_R=()
f_split() {
    : 'It does a "split" into a given string and returns an array.

    Args:
        TARGET_P (str): Target string to "split".
        DELIMITER_P (Optional[str]): Delimiter used to "split". If not 
    informed the split will be done by spaces.

    Returns:
        F_SPLIT_R (array): Array with the provided string separated by the 
    informed delimiter.
    '

    F_SPLIT_R=()
    TARGET_P=$1
    DELIMITER_P=$2
    if [ -z "$DELIMITER_P" ] ; then
        DELIMITER_P=" "
    fi

    REMOVE_N=1
    if [ "$DELIMITER_P" == "\n" ] ; then
        REMOVE_N=0
    fi

    # NOTE: This was the only parameter that has been a problem so far! 
    # By Questor
    # [Ref.: https://unix.stackexchange.com/a/390732/61742]
    if [ "$DELIMITER_P" == "./" ] ; then
        DELIMITER_P="[.]/"
    fi

    if [ ${REMOVE_N} -eq 1 ] ; then

        # NOTE: Due to bash limitations we have some problems getting the 
        # output of a split by awk inside an array and so we need to use 
        # "line break" (\n) to succeed. Seen this, we remove the line breaks 
        # momentarily afterwards we reintegrate them. The problem is that if 
        # there is a line break in the "string" informed, this line break will 
        # be lost, that is, it is erroneously removed in the output! 
        # By Questor
        TARGET_P=$(awk 'BEGIN {RS="dn"} {gsub("\n", "3F2C417D448C46918289218B7337FCAF"); printf $0}' <<< "${TARGET_P}")

    fi

    # NOTE: The replace of "\n" by "3F2C417D448C46918289218B7337FCAF" results 
    # in more occurrences of "3F2C417D448C46918289218B7337FCAF" than the 
    # amount of "\n" that there was originally in the string (one more 
    # occurrence at the end of the string)! We can not explain the reason for 
    # this side effect. The line below corrects this problem! By Questor
    TARGET_P=${TARGET_P%????????????????????????????????}

    SPLIT_NOW=$(awk -F"$DELIMITER_P" '{for(i=1; i<=NF; i++){printf "%s\n", $i}}' <<< "${TARGET_P}")

    while IFS= read -r LINE_NOW ; do
        if [ ${REMOVE_N} -eq 1 ] ; then

            # NOTE: We use "'" to prevent blank lines with no other characters 
            # in the sequence being erroneously removed! We do not know the 
            # reason for this side effect! By Questor
            LN_NOW_WITH_N=$(awk 'BEGIN {RS="dn"} {gsub("3F2C417D448C46918289218B7337FCAF", "\n"); printf $0}' <<< "'${LINE_NOW}'")

            # NOTE: We use the commands below to revert the intervention made 
            # immediately above! By Questor
            LN_NOW_WITH_N=${LN_NOW_WITH_N%?}
            LN_NOW_WITH_N=${LN_NOW_WITH_N#?}

            F_SPLIT_R+=("$LN_NOW_WITH_N")
        else
            F_SPLIT_R+=("$LINE_NOW")
        fi
    done <<< "$SPLIT_NOW"
}

# --------------------------------------
# HOW TO USE
# ----------------

STRING_TO_SPLIT="
 * How do I list all databases and tables using psql?

\"
sudo -u postgres /usr/pgsql-9.4/bin/psql -c \"\l\"
sudo -u postgres /usr/pgsql-9.4/bin/psql <DB_NAME> -c \"\dt\"
\"

\"
\list or \l: list all databases
\dt: list all tables in the current database
\"

[Ref.: /dba/1285/how-do-i-list-all-databases-and-tables-using-psql]


"

f_split "$STRING_TO_SPLIT" "bin/psql -c"

# --------------------------------------
# OUTPUT AND TEST
# ----------------

ARR_LENGTH=${#F_SPLIT_R[*]}
for (( i=0; i<=$(( $ARR_LENGTH -1 )); i++ )) ; do
    echo " > -----------------------------------------"
    echo "${F_SPLIT_R[$i]}"
    echo " < -----------------------------------------"
done

if [ "$STRING_TO_SPLIT" == "${F_SPLIT_R[0]}bin/psql -c${F_SPLIT_R[1]}" ] ; then
    echo " > -----------------------------------------"
    echo "The strings are the same!"
    echo " < -----------------------------------------"
fi

0

複数行の要素の場合、次のようなものではありません

$ array=($(echo -e $'a a\nb b' | tr ' ' '§')) && array=("${array[@]//§/ }") && echo "${array[@]/%/ INTERELEMENT}"

a a INTERELEMENT b b INTERELEMENT

-1

別の方法は次のとおりです。

string="Paris, France, Europe"
IFS=', ' arr=(${string})

これで、要素は「arr」配列に格納されます。要素を反復するには:

for i in ${arr[@]}; do echo $i; done

1
私はこの考えを私の答えでカバーしています間違った回答#5を参照してください(evalトリックに関する私の議論に特に興味があるかもしれません)。あなたの解決策は$IFS、事後的にコンマスペース値に設定されたままにします。
bgoldst 2017

-1

これを解決するには多くの方法があるため、ソリューションで確認したいものを定義することから始めましょう。

  1. Bashはreadarrayこの目的のために組み込みを提供します。使ってみましょう。
  2. 変更IFS、ループ、使用eval、余分な要素の追加などの醜く不必要なトリックを避けてから削除します。
  3. 同様の問題に簡単に適応できる、シンプルで読みやすいアプローチを見つけてください。

readarrayコマンドは、区切り文字として改行を使用するのが最も簡単です。他の区切り文字を使用すると、配列に要素が追加される場合があります。最もクリーンなアプローチは、最初に入力を適切に動作するフォームに適合させることですreadarray渡す前ことです。

この例の入力には、複数文字の区切り文字はありませ。少し常識を適用すると、各要素をトリミングする必要があるかもしれないコンマ区切りの入力として理解するのが一番です。私の解決策は、入力をコンマで複数の行に分割し、各要素をトリミングして、すべてをに渡すことreadarrayです。

string='  Paris,France  ,   All of Europe  '
readarray -t foo < <(tr ',' '\n' <<< "$string" |sed 's/^ *//' |sed 's/ *$//')
declare -p foo

# declare -a foo='([0]="Paris" [1]="France" [2]="All of Europe")'

-2

別のアプローチは次のとおりです。

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

この後、「arr」は4つの文字列を持つ配列です。これは、IFSやreadやその他の特別なものを処理する必要がないため、はるかに単純で直接的です。


他の回答と同じ(悲しいことに一般的な)アンチパターン:単語分割とファイル名拡張の対象となります。
gniourf_gniourf 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.