web-dev-qa-db-ja.com

Bashで配列の要素を結合するにはどうすればいいですか?

Bashにこのような配列があるとします。

FOO=( a b c )

要素をコンマで結合する方法たとえば、a,b,cを生成します。

355
David Wolever

Pascal Pilzによるソリューションを100%純粋なBashの関数として書き換える(外部コマンドなし)。

function join_by { local IFS="$1"; shift; echo "$*"; }

例えば、

join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c

代わりに、@gniourf_gniourfによるアイデアを使用して、printfを使用して複数文字の区切り文字をサポートすることもできます。

function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

例えば、

join_by , a b c #a,b,c
join_by ' , ' a b c #a , b , c
join_by ')|(' a b c #a)|(b)|(c
join_by ' %s ' a b c #a %s b %s c
join_by $'\n' a b c #a<newline>b<newline>c
join_by - a b c #a-b-c
join_by '\' a b c #a\b\c
461

さらに別の解決策:

#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}

echo $bar

編集:同じだが複数文字の可変長セパレータの場合:

#!/bin/bash
separator=")|(" # e.g. constructing regex, pray it does not contain %s
foo=('foo bar' 'foo baz' 'bar baz')
regex="$( printf "${separator}%s" "${foo[@]}" )"
regex="${regex:${#separator}}" # remove leading separator
echo "${regex}"
# Prints: foo bar)|(foo baz)|(bar baz
194
doesn't matters
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
116
Pascal Pilz

例えば、

SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"

echo "$FOOJOIN"
65
martin clayton

驚くべきことに私の解決策はまだ与えられていません:)これは私にとって最も簡単な方法です。それは関数を必要としません:

IFS=, eval 'joined="${foo[*]}"'

注意:この解決方法は非POSIXモードでうまく動作することが確認されています。 POSIXモード では、要素はまだ正しく結合されていますが、IFS=,は永続的になります。

24
konsolebox

これは、仕事をする100%純粋なBash関数です。

join() {
    # $1 is return variable name
    # $2 is sep
    # $3... are the elements to join
    local retname=$1 sep=$2 ret=$3
    shift 3 || shift $(($#))
    printf -v "$retname" "%s" "$ret${@/#/$sep}"
}

見て:

$ a=( one two "three three" four five )
$ join joineda " and " "${a[@]}"
$ echo "$joineda"
one and two and three three and four and five
$ join joinedb randomsep "only one element"
$ echo "$joinedb"
only one element
$ join joinedc randomsep
$ echo "$joinedc"

$ a=( $' stuff with\nnewlines\n' $'and trailing newlines\n\n' )
$ join joineda $'a sep with\nnewlines\n' "${a[@]}"
$ echo "$joineda"
 stuff with
newlines
a sep with
newlines
and trailing newlines


$

これは末尾の改行でさえも保存し、そして関数の結果を得るためにサブシェルを必要としません。 printf -vが気に入らず(なぜ気に入らないのですか?)、変数名を渡しても、返される文字列にグローバル変数を使用できます。

join() {
    # $1 is sep
    # $2... are the elements to join
    # return is in global variable join_ret
    local sep=$1 IFS=
    join_ret=$2
    shift 2 || shift $(($#))
    join_ret+="${*/#/$sep}"
}
21
gniourf_gniourf

配列を文字列としてエコーし、スペースを改行に変換してから、pasteを使用してすべてを1行に結合します。

tr " " "\n" <<< "$FOO" | paste -sd , -

結果:

a,b,c

これは私にとって最も速くてきれいなようです!

12
Yanick Girouard

外部コマンドを使用しない

$ FOO=( a b c )     # initialize the array
$ BAR=${FOO[@]}     # create a space delimited string from array
$ BAZ=${BAR// /,}   # use parameter expansion to substitute spaces with comma
$ echo $BAZ
a,b,c

警告、要素に空白がないことを前提としています。

8
Nil Geisweiller

@を再利用しても解決策は重要ではありませんが、$ {:1}サブ置換を避け、中間変数を必要としないようにして1つのステートメントを使用します。

echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )

printfは、 '書式文字列は引数を満たすのに必要なだけ再利用されます。そのmanページに、文字列の連結が文書化されているように。カットはフィールド数としてLISTの長さだけを保持するので、トリックは最後のsperatorを切り刻むためにLISTの長さを使うことです。

8
Valise
s=$(IFS=, eval 'echo "${FOO[*]}"')
8
eel ghEEz

これは、既存のソリューションとそれほど違いはありませんが、個別の関数を使用することを避け、親シェルのIFSを変更することはなく、すべて1行になります。

arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"

その結果

a,b,c

制限:区切り文字は1文字を超えてはいけません。

7
Benjamin W.
$ set a 'b c' d

$ history -p "$@" | paste -sd,
a,b c,d
4
Steven Penny

次のアイデアをこれまでのところすべての世界の最高を組み合わせます。

# join with separator
join_ws()  { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }

この小さな傑作は

  • 100%純粋なbash(IFSによるパラメータ展開が一時的に設定解除、外部呼び出し、印刷なしなど)
  • コンパクト、完全、完璧(1文字および複数文字のリミッター、空白、改行、その他のシェル特殊文字を含むリミッター、空の区切り文字で動作)
  • 効率的(サブシェルなし、配列コピーなし)
  • シンプルでばかげている、そしてある程度まで、美しく、そしてまた有益

例:

$ join_ws , a b c
a,b,c
$ join_ws '' a b c
abc
$ join_ws $'\n' a b c
a
b
c
$ join_ws ' \/ ' A B C
A \/ B \/ C
4
guest

任意の長さの区切り文字を受け入れるprintfソリューション(@に基づいて回答は関係ありません)

#/!bin/bash
foo=('foo bar' 'foo baz' 'bar baz')

sep=',' # can be of any length
bar=$(printf "${sep}%s" "${foo[@]}")
bar=${bar:${#sep}}

echo $bar
4
Riccardo Galli

トップアンサーの短縮版:

joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }

使用法:

joinStrings "$myDelimiter" "${myArray[@]}"
4
Camilo Martin

私の試み.

$ array=(one two "three four" five)
$ echo "${array[0]}$(printf " SEP %s" "${array[@]:1}")"
one SEP two SEP three four SEP five
1
Ben Davis

これまでのところ、私の最高の世界の組み合わせについての詳細なコメントをありがとうございました@gniourf_gniourf。完全に設計されテストされていないコードを投稿して申し訳ありません。これがより良い方法です。

# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }

概念によるこの美しさは

  • (まだ)100%純粋なbash(printfも同様に組み込まれていることを明示的に指摘してくれてありがとう。以前はこれについて知りませんでした...)
  • 複数文字の区切り文字を扱う
  • よりコンパクトでより完全で、今度は慎重に考え抜かれ、シェルスクリプトからのランダムな部分文字列で長期的にストレステストが行​​われました。そして、コーナーケースや、まったく議論がないような他の些細なこと。それはそれ以上のバグがないことを保証するものではありませんが、それを見つけるのは少し難しい課題になるでしょう。ところで、現在のトップ投票の回答や関連するものでさえ、そのようなことに苦しんでいます。

追加の例

$ join_ws '' a b c
abc
$ join_ws ':' {1,7}{A..C}
1A:1B:1C:7A:7B:7C
$ join_ws -e -e
-e
$ join_ws $'\033[F' $'\n\n\n'  1.  2.  3.  $'\n\n\n\n'
3.
2.
1.
$ join_ws $ 
$
1
guest

配列を直接参照するために変数の間接参照を使用することもできます。名前付き参照も使用できますが、4.3で使用可能になりました。

この形式の関数を使用する利点は、区切り文字をオプション(デフォルトはデフォルトのIFSの最初の文字、スペース)、必要に応じて空の文字列にすることができることです。最初はパラメータとして渡され、次に関数内で"$@"として渡されます。

この解決法では、ユーザーがコマンド置換の中で関数を呼び出す必要もありません。これは、別の変数に割り当てられた文字列の結合バージョンを取得するために、サブシェルを呼び出します。

デメリットとしては、正しいパラメーター名を渡すときには注意が必要です。__rを渡すと__r[@]になります。他の形式のパラメータも拡張するための変数の間接化の振る舞いも明示的に文書化されていません。

function join_by_ref {
    __=
    local __r=$1[@] __s=${2-' '}
    printf -v __ "%s${__s//\%/%%}" "${!__r}"
    __=${__%${__s}}
}

array=(1 2 3 4)

join_by_ref array
echo "$__" # Prints '1 2 3 4'.

join_by_ref array '%s'
echo "$__" # Prints '1%s2%s3%s4'.

join_by_ref 'invalid*' '%s' # Bash 4.4 shows "invalid*[@]: bad substitution".
echo "$__" # Prints nothing but newline.

これは3.1から5.0-alphaまで動作します。観察されたように、変数の間接指定は変数だけでなく他のパラメータでも機能します。

パラメータは値を格納するエンティティです。これは、名前、番号、または特殊パラメータの下にリストされている特殊文字のいずれかです。変数は名前で示されるパラメータです。

配列と配列要素もパラメータ(値を格納するエンティティ)であり、配列への参照も技術的にはパラメータへの参照です。そして、特別なパラメータ@と同様に、array[@]も有効な参照を行います。

パラメータ自体から参照を外す、変更された、または選択的な展開形式(部分文字列展開など)は機能しません。

1
konsolebox

今使っている:

TO_IGNORE=(
    E201 # Whitespace after '('
    E301 # Expected N blank lines, found M
    E303 # Too many blank lines (pep8 gets confused by comments)
)
ARGS="--ignore `echo ${TO_IGNORE[@]} | tr ' ' ','`"

これはうまくいきますが、(一般的なケースでは)配列要素にスペースがあると恐ろしく壊れます。

(興味のある方には、これは pep8.py のラッパースクリプトです。)

1
David Wolever

これはほとんどのPOSIX互換シェルがサポートしているものです:

join_by() {
    # Usage:  join_by "||" a b c d
    local arg arr=() sep="$1"
    shift
    for arg in "$@"; do
        if [ 0 -lt "${#arr[@]}" ]; then
            arr+=("${sep}")
        fi
        arr+=("${arg}") || break
    done
    printf "%s" "${arr[@]}"
}
1
Mehrdad

結合したい要素が単なるスペースで区切られた文字列の配列ではない場合は、次のようにすることができます。

foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
    'aa','bb','cc','dd'

たとえば、私のユースケースは、シェルスクリプトでいくつかの文字列が渡されることで、これを使用してSQLクエリを実行する必要があります。

./my_script "aa bb cc dd"

My_scriptでは、 "SELECT * FROM table WHERE name IN( 'aa'、 'bb'、 'cc'、 'dd')を実行する必要があります。それから上記のコマンドが役に立ちます。

1
Dexin Wang

複数文字の区切り文字にはPerlを使用してください。

function join {
   Perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; 
}

join ', ' a b c # a, b, c

または一行で:

Perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
1
dpatru

この方法では値内のスペースを処理しますが、ループが必要です。

#!/bin/bash

FOO=( a b c )
BAR=""

for index in ${!FOO[*]}
do
    BAR="$BAR,${FOO[$index]}"
done
echo ${BAR:1}
0
dengel

x=${"${arr[*]}"// /,}

これが最短の方法です。

例、

arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5
0
user31986

ループで配列を作成する場合、これは簡単な方法です:

arr=()
for x in $(some_cmd); do
   arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
0
Ian Kelling