Друкуйте аргументи оболонки у зворотному порядку


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: Ні, це не домашнє завдання, я вивчаю оболонку для іспиту, тому намагаюся вирішувати старі іспити.

Відповіді:


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є або названий параметр, або число (позначає позиційний параметр). Поки ви перебуваєте на цьому, ви можете використовувати інші функції зручності.

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}і (()). Якщо ви хочете дотримуватися стандартних ш, вони недоступні, але $((…))є.
Жил 'ТАК - перестань бути злим'

Гаразд, я думаю, що я можу працювати з цим :)
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

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


Зауважте, що цей (всупереч іншим відповідям, розміщеним дотепер) також працював би в оболонці Борна (не те, що в даний час немає жодної причини використовувати цю оболонку)
Stéphane Chazelas

@ StéphaneChazelas: Навіщо цитувати $#? Чи може це бути що-небудь, крім невід’ємного цілого числа?
G-Man каже: "Відновіть Моніку"

2
@ G-Man, залишаючи змінну без котирування у контексті списку, викликає оператор split + glob. Немає причини, чому ви хочете посилатися на це тут. Це не має нічого спільного зі змістом змінної. Дивіться також unix.stackexchange.com/a/171347 до кінця. Також зауважте, що деякі оболонки (тире, шикарний, наприклад) все ще успадковують IFS з навколишнього середовища.
Стефан Шазелас

Я виявив, що на Android я повинен був заявитиlocal arg=$1
starfry

2

Це правильне і небезпечне використання eval. Ви повністю контролюєте вміст, який ви використовуєте eval.

Якщо це все-таки викликає погані почуття, тоді, якщо ви не переймаєтесь переносимістю, ви можете використовувати ${!i}синтаксис непрямості Баша .


значить ${!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

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на поведінку поведінки впливає виходячи із значення повернення попередньої команди - що завжди :є нульовою командою, і так зазвичай істинно. 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

-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done

2
взагалі ніяких пояснень, приємно. Від мене зворотний голос. Поясніть, що робить ваш код, і я можу змінити свою думку.
WarrenFaith

3
Можна спростити доfor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas

Я галюциную? Я подумав, що бачив слова у питанні, яке говорило «надрукуйте аргументи ... крім третього та четвертого».
G-Man каже: "Відновіть Моніку"

1
@ G-Man, в заголовку написано "надрукувати аргументи зворотно", на що це відповідає, не використовуючи eval. Виключення 3 і 4 з цього є тривіальним. Я не думаю, що низовики є виправданими. Це також зрозуміло (хоча, як я вже сказав, це може бути спрощено багато).
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.