Bash-配列を逆にします


16

配列を逆にする簡単な方法はありますか?

#!/bin/bash

array=(1 2 3 4 5 6 7)

echo "${array[@]}"

だから私は得るだろう:7 6 5 4 3 2 1
代わりに:1 2 3 4 5 6 7

回答:


14

質問に書かれたとおりに答えましたが、このコードは配列を逆にします。(配列を逆にせずに要素を逆順に印刷することはfor、最後の要素からゼロまでカウントダウンするループです。)これは、標準の「最初と最後のスワップ」アルゴリズムです。

array=(1 2 3 4 5 6 7)

min=0
max=$(( ${#array[@]} -1 ))

while [[ min -lt max ]]
do
    # Swap current first and last elements
    x="${array[$min]}"
    array[$min]="${array[$max]}"
    array[$max]="$x"

    # Move closer
    (( min++, max-- ))
done

echo "${array[@]}"

奇数と偶数の長さの配列に対して機能します。


これはスパース配列では機能しないことに注意してください。
アイザック

@Isaacを処理する必要がある場合、StackOverflowに解決策があります。
ロアイマ


17

別の型破りなアプローチ:

#!/bin/bash

array=(1 2 3 4 5 6 7)

f() { array=("${BASH_ARGV[@]}"); }

shopt -s extdebug
f "${array[@]}"
shopt -u extdebug

echo "${array[@]}"

出力:

7 6 5 4 3 2 1

extdebugが有効になっている場合、配列にBASH_ARGVはすべての位置パラメータが逆の順序で関数に含まれます。


これは素晴らしいトリックです!
バレンティンバ

14

型破りなアプローチ(すべて純粋ではありませんbash):

  • 配列内のすべての要素が1文字だけの場合(質問のように)、次を使用できますrev

    echo "${array[@]}" | rev
  • さもないと:

    printf '%s\n' "${array[@]}" | tac | tr '\n' ' '; echo
  • 使用できる場合zsh

    echo ${(Oa)array}

ただ見上げるだけでtaccat覚えておくと良い反対のように、ありがとう!
ナス

3
のアイデアは好きですが、2桁の数字では正しく機能しないrevことに注意する必要がありrevます。たとえば、12 rev を使用する配列要素はとして出力され21ます。試してみてください;-)
ジョージヴァシリウ

@GeorgeVasiliouはい、すべての要素が1文字(数字、文字、句読点など)の場合にのみ機能します。だからこそ、より一般的な2番目の解決策も挙げました。
jimmij

8

実際に別の配列で逆をしたい場合:

reverse() {
    # first argument is the array to reverse
    # second is the output array
    declare -n arr="$1" rev="$2"
    for i in "${arr[@]}"
    do
        rev=("$i" "${rev[@]}")
    done
}

次に:

array=(1 2 3 4)
reverse array foo
echo "${foo[@]}"

与える:

4 3 2 1

これは、配列インデックスが欠落array=([1]=1 [2]=2 [4]=4)している場合、たとえば0から最高のインデックスまでループすると、空の要素が追加される可能性がある場合に、正しく処理する必要があります。


何らかの理由で次のshellcheck2つの警告が出力されますが、これは非常にうまく機能しますarray=(1 2 3 4) <-- SC2034: array appears unused. Verify it or export it.echo "${foo[@]}" <-- SC2154: foo is referenced but not assigned.
17

1
@nathは間接的に使用されますが、それがdeclareラインの目的です。
muru

賢いですが、declare -n4.3より前のbashバージョンでは動作しないようです。
G-Manは「

8

配列の位置を適切に入れ替えるには(スパース配列であっても)(bash 3.0以降):

#!/bin/bash
# Declare an sparse array to test:
array=([5]=101 [6]=202 [10]=303 [11]=404 [20]=505 [21]=606 [40]=707)
echo "Initial array values"
declare -p array

swaparray(){ local temp; temp="${array[$1]}"
             array[$1]="${array[$2]}"
             array[$2]="$temp"
           }

ind=("${!array[@]}")                         # non-sparse array of indexes.

min=-1; max="${#ind[@]}"                     # limits to one before real limits.
while [[ min++ -lt max-- ]]                  # move closer on each loop.
do
    swaparray "${ind[min]}" "${ind[max]}"    # Exchange first and last
done

echo "Final Array swapped in place"
declare -p array
echo "Final Array values"
echo "${array[@]}"

実行時:

./script
Initial array values
declare -a array=([5]="101" [6]="202" [10]="303" [11]="404" [20]="505" [21]="606" [40]="707")

Final Array swapped in place
declare -a array=([5]="707" [6]="606" [10]="505" [11]="404" [20]="303" [21]="202" [40]="101")

Final Array values
707 606 505 404 303 202 101

古いbashの場合、ループ(bash(2.04以降))を使用$aし、末尾のスペースを避けるために使用する必要があります。

#!/bin/bash

array=(101 202 303 404 505 606 707)
last=${#array[@]}

a=""
for (( i=last-1 ; i>=0 ; i-- ));do
    printf '%s%s' "$a" "${array[i]}"
    a=" "
done
echo

2.03以降のbashの場合:

#!/bin/bash
array=(101 202 303 404 505 606 707)
last=${#array[@]}

a="";i=0
while [[ last -ge $((i+=1)) ]]; do 
    printf '%s%s' "$a" "${array[ last-i ]}"
    a=" "
done
echo

また(ビットごとの否定演算子を使用)(bash 4.2以降):

#!/bin/bash
array=(101 202 303 404 505 606 707)
last=${#array[@]}

a=""
for (( i=0 ; i<last ; i++ )); do 
    printf '%s%s' "$a" "${array[~i]}"
    a=" "
done
echo

4.3より前のbashバージョンでは、配列の要素を末尾から負の添え字で後方にアドレス指定することはできないようです。
G-Manが「Reinstate Monica」と言う

1
実際、4.2-alphaでは負の数値のアドレスが変更されました。そして、否定された値を持つスクリプトは、そのバージョンから動作します。@ G-Man p。 今最大割り当てられたインデックス+ 1からのオフセットとして扱わインデックス配列に負の添え字、しかしバッシュ、ハッカーレポート誤っ4.1 数値インデックス付け配列は負のインデックスを使用して端部からアクセスすることができる
アイザック

3

glyい、維持できないが、ワンライナー:

eval eval echo "'\"\${array['{$((${#array[@]}-1))..0}']}\"'"

シンプルな、しかし短いません:eval eval echo "'\"\${array[-'{1..${#array[@]}}']}\"'"
アイザック

スパース配列の場合でも:ind=("${!array[@]}");eval eval echo "'\"\${array[ind[-'{1..${#array[@]}}']]}\"'"
アイザック

@Isaacしかし、残念ながら残念なことに、スパース配列バージョンでは1行ではなく、くてメンテナンスが不可能です。(ただし、小さな配列のパイプよりも高速であるべきです。)
user23013

技術的には、「ワンライナー」です。はい、1つのコマンドではなく、「1つのライナー」です。私は同意します、はい、非常にいとメンテナンスの問題ですが、遊ぶのは楽しいです。
アイザック

1

私は何か新しいことを言うつもりはなくtac、配列を逆にするためにも使用しますが、それはbashバージョン4.4を使用した以下の単一行ソリューションに言及する価値があるでしょう:

$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}" |tac)

テスト:

$ array=(1 2 3 4 5 6 10 11 12)
$ echo "${array[@]}"
1 2 3 4 5 6 10 11 12
$ read -d'\n' -a array < <(printf '%s\n' "${array[@]}"|tac)
$ echo "${array[@]}"
12 11 10 6 5 4 3 2 1

read内の変数名は元の配列の名前であるため、一時ストレージにヘルパー配列は必要ありません。

IFSの調整による代替実装:

$ IFS=$'\n' read -d '' -a array < <(printf '%s\n' "${array[@]}"|tac);declare -p array
declare -a array=([0]="12" [1]="11" [2]="10" [3]="6" [4]="5" [5]="4" [6]="3" [7]="2" [8]="1")

PS:上記のソリューションは、bash組み込み関数の実装が異なるため、以下のbashバージョンでは機能しないと思います。4.4read


IFSバージョンは動作しますが、それはまた、印刷されていますdeclare -a array=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6" [6]="10" [7]="11" [8]="12")。bashを使用し4.4-5ます。あなたは削除するようになった;declare -p array...それは動作しますが、最初の行の末尾に
ナス

1
@nath declare -pは、bashに実際の配列(インデックスとコンテンツ)を出力させる簡単な方法です。declare -p実際のスクリプトではこのコマンドは必要ありません。配列の割り当てに問題が発生した場合、その結果${array[0]}="1 2 3 4 5 6 10 11 12"=すべての値が同じインデックスに格納されている場合があります。エコーを使用すると、違いは見られません。を使用して配列をすばやく印刷するにdeclare -p arrayは、実際の配列インデックスと各インデックスの対応する値を返します。
ジョージヴァシリウ

@nathところで、このread -d'\n'方法はうまくいきませんでしたか?
ジョージヴァシリウ

read -d'\n'正常に動作します。
ナス


1

任意の配列(任意の値を持つ任意の数の要素を含む可能性がある)を反転するには:

zsh

array_reversed=("${(@Oa)array}")

ではbash4.4以降、ことを考えるとbash変数がNULはとにかくバイトを含めることはできません、あなたはGNUを使用することができますtac -s ''NULがレコードを区切りとして、印刷された要素に:

readarray -td '' array_reversed < <(
  ((${#array[@]})) && printf '%s\0' "${array[@]}" | tac -s '')

POSIXly、(POSIXシェル配列逆にすること$@で作られたが、$1$2...):

code='set --'
n=$#
while [ "$n" -gt 0 ]; do
  code="$code \"\${$n}\""
  n=$((n - 1))
done
eval "$code"

1

純粋なbashソリューションは、ワンライナーとして機能します。

$: for (( i=${#array[@]}-1; i>=0; i-- ))
>  do rev[${#rev[@]}]=${array[i]}
>  done
$: echo  "${rev[@]}"
7 6 5 4 3 2 1

良いですね!!!THX; ここでコピーする1つのライナー:-) `array =(1 2 3 4 5 6 7); for((i = $ {#array [@]}-1; i> = 0; i--)); do rev [$ {#rev [@]}] = $ {array [i]}; 完了; echo "$ {rev [@]}" `
21:14に

行うことrev+=( "${array[i]}" )は簡単に思えます。
アイザック

1つのうち6つ、他の6つ。私はその構文を好みませんが、その理由はありません-偏見と好みだけです。あなたはあなたをします。
ポールホッジズ

-1

また、使用を検討することができます seq

array=(1 2 3 4 5 6 7)

for i in $(seq $((${#array[@]} - 1)) -1 0); do
    echo ${array[$i]}
done

freebsdでは、-1増分パラメーターを省略できます。

for i in $(seq $((${#array[@]} - 1)) 0); do
    echo ${array[$i]}
done

これは配列を逆にするのではなく、単に逆の順序で出力することに注意してください。
ロアイマ

同意して、私のポイントは、インデックスアクセスを代替手段として考慮することでもありました。
M. Modugno

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