Вывести аргументы оболочки в обратном порядке

12

Я немного застрял. Моя задача - напечатать аргументы моего сценария в обратном порядке, кроме третьего и четвертого.

Что у меня есть этот код:

#!/bin/bash

i=$#
for arg in "$@"
do
    case $i
    in
        3) ;;
        4) ;;
        *) eval echo "$i. Parameter: \$$i";;
    esac
    i=`expr $i - 1`
done

Поскольку я ненавижу eval (привет PHP), я ищу решение без него, но не могу найти его.

Как я могу определить положение аргумента динамически?

PS: Нет, это не домашняя работа, я изучаю оболочку для экзамена, поэтому я пытаюсь решить старые экзамены.

WarrenFaith
источник

Ответы:

13

evalявляется единственным переносимым способом доступа к позиционному параметру по его динамически выбранной позиции. Ваш сценарий будет более понятным, если вы явно зациклились на индексе, а не на значениях (которые вы не используете). Обратите внимание, что вам не нужно, exprесли вы не хотите, чтобы ваш сценарий выполнялся в античных оболочках Борна; $((…))арифметика в POSIX. Ограничить использование evalдо минимально возможного фрагмента; Например, не используйте eval echo, присвойте значение временной переменной.

i=$#
while [ "$i" -gt 0 ]; do
  if [ "$i" -ne 3 ] && [ "$i" -ne 2 ]; then
    eval "value=\${$i}"
    echo "Parameter $i is $value"
  fi
  i=$((i-1))
done

В bash вы можете использовать ${!i}значение параметра, имя которого равно $i. Это работает, когда $iявляется либо именованным параметром, либо числом (обозначающим позиционный параметр). Пока вы работаете над этим, вы можете использовать другие удобные функции bash.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
Жиль "ТАК - перестань быть злым"
источник
Я не могу использовать, argпоскольку они заказаны правильно, а не в обратном порядке. Для использования exprя ограничен в использовании только стандарта.
WarrenFaith
1
@WarrenFaith Если ваш скрипт начинается с #!/bin/bash, вы можете использовать ${!i}и (()). Если вы хотите придерживаться стандартного sh, они недоступны, но $((…))есть.
Жиль "ТАК - перестань быть злым"
Хорошо, я думаю, что я могу работать с этим :)
WarrenFaith
Обратите внимание, что античные снаряды Борна в любом случае не смогут получить доступ к позиционным параметрам, превышающим 9 долларов.
Стефан Шазелас
1
evalэто не единственный портативный способ , как Райан показал .
Стефан Шазелас
7

Я держу скрипт reverseна моем пути, который делает это:

#!/bin/sh

if [ "$#" -gt 0 ]; then
    arg=$1
    shift
    reverse "$@"
    printf '%s\n' "$arg"
fi

Пример использования:

$ reverse a b c '*' '-n'
-n
*
c
b
a

Вы также можете использовать функцию вместо выделенного скрипта.

Райан Уильямс
источник
Обратите внимание, что один (вопреки другим ответам, опубликованным до сих пор) также будет работать в оболочке Bourne (не то, чтобы в настоящее время есть какая-либо причина использовать эту оболочку)
Стефан Шазелас
@ StéphaneChazelas: почему цитата $#? Может ли это быть что-то кроме неотрицательного целого числа?
G-Man говорит «Восстановить Монику»
2
@ G-Man, оставляя переменную без кавычек в контексте списка, вызывает оператор split + glob. Нет причины, почему вы хотели бы вызвать это здесь. Это не имеет никакого отношения к содержимому переменной. Смотрите также unix.stackexchange.com/a/171347 ближе к концу. Также обратите внимание, что некоторые оболочки (например, тире, шикарные) все еще наследуют IFS из среды.
Стефан Шазелас
Я обнаружил, что на Android я должен был объявитьlocal arg=$1
starfry
2

Это правильное и неопасное использование eval. Вы полностью контролируете контент, который вы используете eval.

Если это все еще вызывает у вас плохие чувства, тогда, если вас не волнует переносимость, вы можете использовать ${!i}синтаксис косвенного обращения Bash .

Шон Дж. Гофф
источник
так ${!i}не входит ли в стандартный синтаксис?
WarrenFaith
Нет, это башизм. Вот расширения параметров POSIX: pubs.opengroup.org/onlinepubs/009695399/utilities/…
Шон Дж. Гофф
2

Предполагая, что позиционные параметры не содержат символов новой строки:

[ "$#" -gt 0 ] && printf '%s\n' "$@" | #print in order
sed '3,4 d' |                          #skip 3 and 4
tac                                    #reverse (try tail -r on
                                       #non-GNU systems).

Тестовое задание:

set 1 2 3 4 5 6
printf '%s\n' "$@" | 
sed '3,4 d' |
tac

Тестовый вывод:

6
5
2
1
PSkocik
источник
1

С zsh:

$ set a 'foo bar' c '' '*'
$ printf '<%s>\n' "${(Oa)@}"
<*>
<>
<c>
<foo bar>
<a>

Oaявляется флагом раскрытия параметров для сортировки элементов массива при расширении по обратным индексам массива.

Исключить 3 и 4:

$ printf '<%s>\n' "${(Oa)@[5,-1]}" "${(Oa)@[1,2]}"
<*>
<foo bar>
<a>
Стефан Шазелас
источник
0

С практически любой оболочкой:

printf '{ PS4=\${$(($#-$x))}; } 2>&3; 2>&1\n%.0s' |
x=LINENO+1 sh -sx "$@" 3>/dev/null

И вам не нужно использовать подоболочки. Например:

set -x a b c
{ last= PS4=\${last:=\${$#}}; set +x; } 2>/dev/null
echo "$last"

... печать ...

c

А вот функция оболочки, которая может установить aliasдля вас оболочку , которая будет печатать аргументы вперед или назад:

tofro() case $1 in (*[!0-9]*|'') ! :;;(*) set "$1"
        until   [ "$1" -eq "$(($#-1))" ]    &&
                shift && alias args=":; printf \
            \"%.\$((\$??\${#*}:0))s%.\$((!\$??\${#*}:0))s\n\" $* "
        do      [ "$#" -gt 1 ] &&
                set "$@ \"\${$#}\" " '"${'"$((1+$1-$#))"'}"' ||
                set "$1" '"$1" "${'"$1"'}"'
        done;   esac

Он не пытается сохранить литеральные значения для каких-либо аргументов, скорее он помещает строку, подобную этой, в args alias:

:;printf    "%.$(($??${#*}:0))s%.$((!$??${#*}:0))s\n" \
            "$1" "${3}" "${2}"  "${2}" "${3}"  "${1}"

... и сохраняет только ссылки на параметры в прямом и обратном направлении. Он будет хранить до количества, указанного в качестве аргумента. И так выше aliasбыло сгенерировано как:

tofro 3

printfПоведение зависит от возвращаемого значения предыдущей команды, которая всегда :является пустой командой, и поэтому обычно имеет значение true. printfбудет пропускать половину своих аргументов каждый раз, когда он печатает, что по умолчанию выводит аргументы с наименьшего номера на самый большой. Однако, если вы просто делаете:

! args

... он печатает их в обратном порядке.

Поскольку псевдоним не хранит никаких литеральных значений, его значение остается статическим, в то время как фактические аргументы могут измениться, но он все равно будет ссылаться на столько, сколько мог бы. Например:

set one two three
tofro 3
args; ! args
shift; args; ! args

... который печатает ...

one
two
three
three
two
one
two
three


three
two

Но сброс псевдонима можно сделать так:

tofro 2
args; ! args

... и так печатает ...

two
three
three
two
mikeserv
источник
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
user119141
источник
2
вообще никаких объяснений, приятно. Даунвот от меня. Объясните, что делает ваш код, и я могу передумать.
WarrenFaith
3
Может быть упрощено доfor ((i = $# - 1; i >= 0; i--))
Стефан Шазелас
Я галлюцинирую? Мне показалось, что я видел слова в вопросе, в которых говорилось «напиши аргументы ... кроме третьего и четвертого».
G-Man говорит: «Восстанови Монику»
1
@ G-Man, заголовок гласит «напечатать аргументы в обратном порядке», на что он отвечает, не используя eval. Исключить 3 и 4 из этого тривиально. Я не думаю, что отрицательные голоса оправданы. Это также говорит само за себя (хотя, как я уже сказал, это может быть значительно упрощено).
Стефан Шазелас