私は配列を使用して"$@"
を逆にすることが可能であることを知っています:
arr=( "$@" )
そして この答えを使用して 、配列を逆にします。
しかし、それには配列を持つシェルが必要です。
tac
を使用することもできます。
set -- $( printf '%s\n' "$@" | tac )
ただし、パラメーターにスペース、タブ、または改行($IFS
のデフォルト値を想定)またはワイルドカード文字が含まれている場合(グロビングが事前に無効にされていない場合)、空の要素が削除され、GNU tac
コマンド(tail -r
の使用は、GNUシステムの外部では若干移植性がありますが、一部の実装では大きな入力で失敗します)。
配列を使用せずにシェルの位置引数を移植可能に逆にする方法はありますか?それは、引数に空白、改行、ワイルドカードが含まれていたり、空の場合でも機能しますか?
移植性があり、配列は必要なく(位置パラメータのみ)、スペースと改行で機能します。
flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done
例:
$ set -- one "two 22" "three
> 333" four
$ printf '<%s>' "$@"; echo
<one><two 22><three
333><four>
$ flag=''; for a in "$@"; do set -- "$a" ${flag-"$@"}; unset flag; done
$ printf '<%s>' "$@"; echo
<four><three
333><two 22><one>
flag
の値は、${flag-"$@"}
の展開を制御します。 flag
が設定されると、flag
の値に展開されます(空の場合でも)。したがって、flag
がflag=''
の場合、${flag....}
は空の値に展開され、引用符で囲まれていないため、シェルによって削除されます。 flag
が設定解除されると、${flag-"$@"}
の値は-
の右側の値に展開されます。つまり、"$@"
の展開なので、すべてになります位置引数(引用、空の値は消去されません)。さらに、変数flag
は消去(設定解除)され、次のコードには影響しません。
一時ストレージに配列を使用しない場合は、for
ループalwaysが変化しない静的セットを反復するという事実を使用できます要素。ある意味では、ループitselfを位置パラメータの一時的なストレージとして使用して、リストを逆の順序で再構築できます。
これを行うには、最初の反復でリストを空にする必要もあります。以下のコードは、単純なフラグを使用して、これを実行する必要があるかどうかを検出します。リストが空になると、フラグが切り替えられます。
flag=true
for value do
if "$flag"; then
set --
flag=false
fi
set -- "$value" "$@"
done
位置パラメータのリストが各反復で効果的に再構築されるため、これは残念ながら非常に低速です(set -- some-list
はすべての位置パラメータを設定します)。 bash
シェルは、1〜10000の整数を反転するのに約50秒かかりますが、zsh
は15秒強かかります。
Isaacのトリック を${flag-"$@"}
とともに使用すると(flag
が設定されていない場合のみ"$@"
に展開されます)、実際には全体の動作が遅くなります。 bash
では1分50秒(!)、zsh
では25秒。
これは、シェルが$flag
でテストを実行する方法、および"$@"
を展開して${flag-"$@"}
を展開する方法の実装の特殊性が原因であると想定しています(シェルは"$@"
内部的に2回?).
一時的なストレージとして配列を使用することを許可する場合(これは標準ではありませんが、それでもかなりportableスクリプトを記述しているシェルがわかっていることが多いため、現在の値を格納するインデックスとして値$#
(位置パラメーターの数)を使用できます。定位置パラメーターのループ。各反復でshift
を使用してこの値を減らすと、配列の末尾から先頭に向かって値を挿入する効果が得られます。
bash
では、配列はインデックス0から始まり、shift
は割り当ての後に来るため、最後の定位置パラメーターは0ではなくインデックス1に格納されます。これはコードに影響を与えません。 bash
で動作しますが、正しい結果が生成されますが、zsh
でも動作します(デフォルトでは1ベースの配列インデックスを使用します)。
コード:
tmp=()
for value do
tmp[$#]=$value
shift
done
set -- "${tmp[@]}"
bash
またはzsh
では、約0.6秒を使用して、1〜10000の整数を反転させます。
この私の答え から Bash-globを使用して反転されたファイルリストを出力する からコピーして、POSIXlyの位置パラメータのリストを反転します。
eval "set -- $(awk 'BEGIN {for (i = ARGV[1]; i; i--) printf " \"${"i"}\""}' "$#")"
または、いくつかの行で少し読みやすくします:
eval "set -- $(
awk '
BEGIN {
for (i = ARGV[1]; i; i--)
printf " \"${" i "}\""
}' "$#"
)"
awk
を使用して、set -- "${3}" "${2}" "${1}"
に3つの要素がある場合に、eval
の"$@"
シェルコードを生成して解釈できるようにするというアイデア。
大きなリストの場合、シェルループ、特に各反復でリストを再構築するループを使用するよりも、かなり高速になる可能性があります。 awk
コードは、(@ mosvyがコメントで示したように)同じ出力を与えるシェルループで置き換えることができますが、bash5 + gawk4.1を使用したテストでは、非常に短いリストを除いて、速度が2倍になります。
zsh
では、配列を逆にするように明示的に設計されたOa
パラメータフラグを使用します。
set -- "${(Oa)@}"
私のシステム(@Kusalanandaのものより少し遅い)、およびset $(seq 10000)
で取得された位置パラメーターのリストで、bash5 + gawk4.2.1を使用すると、そのeval
アプローチは0.4秒かかります @ Kusalananda's = 1分かかります @ Isaac's 2分かかります(zsh
のOa
アプローチには約2ミリ秒かかります)。
Busybox 1.30.1のsh
およびawk
では、これらのタイミングはそれぞれ0.06秒、11秒、11秒になります。