シェルパラメータをどのように変更しますか?

シェルパラメータをどのように変更しますか?

"$@"私は配列を使って反転することが可能であることを知っています。

arr=( "$@" )

そしてこの回答を使用してください、配列を反転します。

ただし、これには配列を含むシェルが必要です。

可能tac:

set -- $( printf '%s\n' "$@" | tac )

ただし、引数に空白、タブ、または改行$IFS(デフォルトは仮定)が含まれているか、ワイルドカードが含まれている(ワイルドカードが事前に無効になっていない場合)、空の要素が削除され、GNUtacコマンド(tail -rGNUシステムの外部で使用されます)が少しより移植性がある場合、これは中断されます。しかし、いくつかの実装は大きな入力のために失敗します)。

配列を使用せずにシェル位置パラメータを移植可能に戻し、パラメータに空白、改行、またはワイルドカードが含まれているか空になる可能性がある場合でも、そのメソッドを機能させる方法はありますか?

答え1

移植可能で、配列は必要なく(位置引数のみ)、スペースと改行を使用できます。

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-"$@"}。 setの場合(空の場合でも)flag値に展開されます。flagしたがって、 flagisのflag=''場合は${flag....}nullに展開され、引用符がないため、シェルから削除されます。設定されていない場合、flagの値は の拡張である${flag-"$@"}右の値に拡張されるので、すべての位置引数になります (引用符を含む、null 値は削除されません)。また、変数は最終的に削除(設定解除)され、後続のコードには影響しません。-"$@"flag

答え2

配列を一時記憶域として使用したくない場合は、forループを使用できます。いつも不変の静的要素のセットを繰り返します。ある意味、私たちはループを使うことができますそれ自体リストを逆順に書き換える間、位置引数の一時記憶域として機能します。

これを行うには、最初の反復でリストを消去する必要があります。以下のコードは単純なフラグを使用して、これが必要かどうかを検出します。リストがクリアされるとフラグが切り替わります。

flag=true
for value do
    if "$flag"; then
        set --
        flag=false
    fi

    set -- "$value" "$@"
done

残念ながら、位置パラメータのリストは実際には非常に遅いです。繰り返しごとに再構築set -- some-listすべての位置パラメータを設定します)。シェルはbash1から10000の整数を反転するのに約50秒かかりますが、zsh15秒が少し長くかかります。

使用イサクのトリックwith(設定されていない場合にのみ拡張されます${flag-"$@"})は、実際に全体の実行速度を1分50秒(!)25秒遅くします。"$@"flagbashzsh

$flagこれは、シェルがテストおよび/または拡張拡張を実行する方法"$@"のいくつかの実装の詳細が原因であると仮定します${flag-"$@"}(シェルが"$@"内部で2回拡張される可能性がありますか?)。


配列を一時記憶域として使用することが許可されている場合(これはそうではありません)。基準しかし、まだかなり持ち運べる通常、スクリプトを作成するシェルを知っているので、$#位置引数を繰り返すときにその値(位置引数の数)をインデックスとして使用して現在の値を格納できます。繰り返すたびにこの値を減らすと、shift配列の終わりから先頭まで値を挿入する効果があります。

では、bash配列はインデックス0から始まり、shift割り当て後に発生するため、最後の位置引数はゼロではなくインデックス1に格納されます。これは、コードがどのように機能するかに影響を与えず、bashまだ正しい結果を生成しますが、それは機能しますzsh(デフォルトでは1ベースの配列インデックスを使用)。

パスワード:

tmp=()
for value do
    tmp[$#]=$value
    shift
done

set -- "${tmp[@]}"

bashまたはを使用すると、zsh1から10000の整数を反転するのに約0.6秒かかります。

答え3

コピーした場所私の答え到着Bash - globを使用してリバースファイルリストを印刷する, POSIX 方式で位置引数のリストを反転します。

eval "set -- $(awk 'BEGIN {for (i = ARGV[1]; i; i--) printf " \"${"i"}\""}' "$#")"

あるいは、数行でもう少し明確に説明すると、次のようになります。

eval "set -- $(
  awk '
    BEGIN {
      for (i = ARGV[1]; i; i--)
        printf " \"${" i "}\""
    }' "$#"
)"

たとえば、3つの要素があるときに解釈するシェルコードをawk生成するのに役立つアイデアです。set -- "${3}" "${2}" "${1}"eval"$@"

大きなリストでは、シェルループ、特に各反復でリストを書き換えるループを使用するよりもはるかに高速です。このawkコードは、同じ出力を提供するシェルループで置き換えることができます(@mosvyがコメントで提案したように)。ただし、bash5 + gawk4.1を使用したテストでは、非常に短いリストを除いて、まだ2倍遅いです。

配列を反転するために明示的に設計されたパラメータフラグをzsh使用できます。Oa

set -- "${(Oa)@}"

私のシステム(@Kusalanandaより少し遅い)とbash5 + gawk4.2.1で取得した位置引数リストを使用すると、set $(seq 10000)このeval方法は0.4秒かかります。@クサラナンダ1分かかります@ISAACの2分かかります(zsh方法Oaは約2ミリ秒かかります)。

busybox 1.30.1shでは、awkこの時間がそれぞれ 0.06 秒、11 秒、11 秒になります。

関連情報