Есть ли способ изменить массив $@ в скрипте оболочки POSIX?

Есть ли способ изменить массив $@ в скрипте оболочки POSIX?

Я, возможно, знаю, как использовать readlinkдля получения обычных файлов из SymLinks. У меня была эта очень простая идея заменить любой SymLink, $@переданный моей функции, следующим образом:

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

Теперь мы сдвигаем записи с начала "$@", одну за другой, и добавляем возможно измененные записи в конец списка. Изменение $@в forцикле $@не является проблемой, поскольку forциклы всегда итерируют по статическому списку, т. е. список, который итерируется, оценивается один раз в начале цикла, только.

Цикл выше, как и циклы ниже, сбрасывает весь список позиционных параметров в каждой итерации. Дляогромныйсписок имен файлов, это может вскоре стать довольно медленным. Если в вашей оболочке есть массивы,Ответ Стивена Киттапоказывает один из способов его ускорения.

Цикл можно также записать в немного более короткой форме (вызывая readlinkкаждый элемент),

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

Это используется readlinkв соответствии с вашим вопросом, хотя это и не утилита POSIX.

Обратите внимание, что если выводreadlink заканчивается одним или несколькими переводами строк(кроме того, который добавлен для завершения последней строки вывода), они будут удалены. Это следствие подстановки команды. Если это проблема, добавьте искусственный завершающий символ в подстановку команды, а затем удалите его, как mosvyпредлагаетсяв комментариях:

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

решение2

Обратите внимание, что ни , readlinkни не realpathявляются командами POSIX. В наборе инструментов командной строки POSIX нет realpath()API -like. Здесь вместо readlink(которого существуют различные несовместимые реализации) вы можете использовать , zshкоторый также имеет realpath()встроенную функциональность -like с его :Pмодификатором. В вашем shскрипте:

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:]))))' "$@")"

Связанный контент