POSIXシェルスクリプトで$@配列を変更する方法はありますか

POSIXシェルスクリプトで$@配列を変更する方法はありますか

SymLinks から通常のファイルを取得する方法を知っているかもしれません。関数に渡されるreadlinkSymLink を次のように置き換えるという非常に基本的なアイデアがありました。$@

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f "$file")
done

例としては、 SymLink が挙げられます/etc/os-release -> ../usr/lib/os-release

しかし、私がこれをやると:

set -- '/etc/os-release'; for file in "$@"; do [ -L "$file" ] && file=$(readlink -f "$file"); done; echo "$@"

元の SymLink パスがまだ取得されます。残念です。直接変更できることを期待していました$@が、それが不可能な場合、回避策はありますか?

答え1

元のコード:

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f -- "$file")
done

問題は、fileループ内の設定が位置パラメータのリストの内容に影響を与えないことです。

代わりに、

for file in "$@"; do
    [ -L "$file" ] && file=$(readlink -f -- "$file")
    set -- "$@" "$file"
    shift
done

ここで、 の先頭からエントリを"$@"1 つずつシフトし、変更された可能性のあるエントリをリストの後ろに追加します。ループは常に静的リストを反復処理するため、ループ$@内での変更は問題になりません。つまり、反復処理されるリストは、ループの開始時に 1 回だけ評価されます。for$@for

上記のループは、下記のループと同様に、各反復で位置パラメータのリスト全体をリセットします。巨大なファイル名のリストの場合、これはすぐにかなり遅くなるかもしれません。シェルに配列がある場合は、スティーブン・キットの回答スピードを上げる一つの方法を示します。

ループは、少し短い形式(readlink各要素を呼び出す)で記述することもできます。

for dummy do
    set -- "$@" "$(readlink -f -- "$1")"
    shift
done

これはreadlinkPOSIX ユーティリティではありませんが、質問どおりに使用されます。

出力がreadlink 1つまたは複数の改行で終わる(出力の最後の行を終了するために追加された文字以外)これらは削除されます。これはコマンド置換の結果です。これが問題になる場合は、コマンド置換で末尾の文字を人為的に追加し、次にそれを削除しますmosvyコメント欄:

for file do
    [ -L "$file" ] && file=$(readlink -f -- "$file"; echo x)
    set -- "$@" "${file%x}"
    shift
done

答え2

readlinkも も POSIX コマンドではないことに注意してくださいrealpath。POSIX コマンドライン ツールチェストには、realpath()-like API はありません。ここでは、 (互換性のないさまざまな実装が存在します) の代わりに、 -like 機能が組み込まれており、修飾子も付いている whichreadlinkを使用できます。スクリプトでは、次のようになります。zshrealpath():Psh

eval "set -- $(zsh -c 'print -r ${(qq)argv:P}' zsh "$@")"

zshただし、最初から次のようにスクリプトを記述することもできます。

argv=($argv:P)

python3または、 zsh よりも利用できる可能性が高い場合:

eval "set -- $(
  LC_ALL=C python3 -c '
import sys, os, shlex
print(" ".join(map(shlex.quote, map(os.path.realpath, sys.argv[1:]))))' "$@")"

関連情報