Каковы некоторые полезные варианты -использования eval в Shell?

Итак, я попробовал это, и это работает:

00 * * * * /bin/sleep 45 && pkill -f./run_my_script
01 * * * *./run_my_script
15
23.03.2021, 17:10
9 ответов

Чтобы получить последний аргумент в оболочке POSIX без расширений, таких как Bash & Co. нарезка (т. е. ${@: -1}), можно использовать

eval "v=\${$#}" 

$#не поддается гадким уловкам, так как находится внутри оболочки и может содержат только количество аргументов скрипта/функции.

Это не я придумал, это Stéphane Chazelas в комментарии . Это также упоминается в этом ответе на , почему и когда следует избегать использования eval?.

9
28.04.2021, 22:57

С bash, потому что расширение фигурной скобкипроисходит до Расширение параметров оболочки:

$ char="F"
$ range=( {A.."$char"} )
$ declare -p range
declare -a range=([0]="{A..F}")
$ eval "range=( {A..$char} )"
$ declare -p range
declare -a range=([0]="A" [1]="B" [2]="C" [3]="D" [4]="E" [5]="F")
6
28.04.2021, 22:57

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

Когда я перешел с Windows на Linux на своем рабочем столе, у меня было много пре-существующих файлов .BATи .CMDдля преобразования, и я не собирался переписывать для них логику, поэтому я нашел способ сделать gotoв bash, который работает, потому что goto functionзапускает sedсам по себе, удаляя любые части скрипта, которые не должны запускаться, а затем оценивает их все.

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

#!/bin/bash

# BAT / CMD goto function
function goto
{
    label=$1
    cmd=$(sed -n "/^:[[:blank:]][[:blank:]]*${label}/{:a;n;p;ba};" $0 | 
          grep -v ':$')
    eval "$cmd"
    exit
}

apt update

# Just for the heck of it: how to create a variable where to jump to:
start=${1:-"start"}
goto "$start"

: start
goto_msg="Starting..."
echo $goto_msg
# Just jump to the label:
goto "continue"

: skipped
goto_msg="This is skipped!"
echo "$goto_msg"

: continue
goto_msg="Ended..."
echo "$goto_msg"

# following doesn't jump to apt update whereas original does
goto update

и я совсем не чувствую себя виноватым, как сказал Линус Торвальдс:

From: Linus Torvalds
Subject: Re: any chance of 2.6.0-test*?
Date: Sun, 12 Jan 2003 11:38:35 -0800 (PST)

I think goto's are fine, and they are often more readable than large amounts of indentation. That's especially true if the code flow isn't actually naturally indented (in this case it is, so I don't think using goto is in any way clearer than not, but in general goto's can be quite good for readability).

Of course, in stupid languages like Pascal, where labels cannot be descriptive, goto's can be bad. But that's not the fault of the goto, that's the braindamage of the language designer.

Исходный код кода(изменен, чтобы сделать его менее подверженным ошибкам)
Источник цитаты

Прошло какое-то время с тех пор, как мне понадобился goto, так как большинство этих старых скриптов уже полностью переработаны.

4
28.04.2021, 22:57

Помимо того, что говорили другие, evalтакже можно использовать для упрощения разбора вывода команды, учитывая, что команда может выводить evalдопустимую строку. Например, вместо того, чтобы пытаться проанализировать вывод

xdotool getwindowfocus getwindowgeometry

вы можете передать xdotoolпараметр --shellи просто оценить вывод.

eval "$( xdotool getwindowfocus getwindowgeometry --shell )"

В этом случае xdotoolвыведет что-то похожее на

WINDOW=46137350
X=1290
Y=559
WIDTH=1258
HEIGHT=509
SCREEN=0

и evalв этом выводе объявят эти переменные в вашей оболочке.

3
28.04.2021, 22:57

В некоторых примерах моего собственного «реального -мира» используются -случаи, когда я не мог придумать лучших альтернатив и evalпросто аккуратно выполнял свою работу.


Вариант использования «условное -расширение» -. Здесь я хочу использовать перенаправление, только если $rmsg_pfxимеет какое-то значение:

eval 'printf -- %s%s\\n "$rmsg_pfx" "$line" '"${rmsg_pfx:+>&2}"

Я не мог бы сделать это без eval, потому что тогда бит >&2расширялся бы как аргумент для printf, а не как его перенаправление.

Вместо этого я мог бы продублировать эту строку, чтобы учесть, что $rmsg_pfxпусто или нет, но это было бы… ну… дублированием кода.


Говоря о перенаправлениях и в случае «косвенного» использования -, мне нравится полагаться на синтаксис перенаправления {varname}>&..., который я эмулирую POSIX, как показано ниже:

# equivalent of bash/ksh `exec {rses_fd0}>&- {rses_fd1}<&-` redirection syntax
eval "exec $rses_fd0>&- $rses_fd1<&-"

Вышеупомянутое предназначено для закрытия fds, и аналогичным образом я делаю аналогичную косвенность для эмуляции открытия fds. Очевидно, что $rses_fd0и $rses_fd1являются внутренними переменными скрипта, полностью находящимися под его контролем от начала до конца.


Иногда мне приходилось использовать eval, чтобы просто «защитить» фрагменты кода оболочки, предназначенные для определенных оболочек, не нарушая работу других.

Например, приведенный ниже фрагмент кода взят из скрипта, который должен быть переносимым (POSIXly ), а также включать несколько оптимизаций, специфичных для оболочки -:

sochars='][ (){}:,!'"'\\"
# NOTE: wrapped in an eval to protect it from dash which croaks over the regex
eval 'o=; while [[ "$s" =~ ([^$sochars]*)([$sochars])(.*) ]]; do
   ...
done'

dashпросто задыхается от неизвестного (, но прямого )синтаксиса на лексическом уровне, даже если такой синтаксис никогда не попадает в реальный путь кода -.


Еще один случай использования «защиты» -в другом смысле. Иногда меня просто не беспокоит необходимость придумывать «маловероятные» имена для целей сохранения и восстановления. Например, в случае ниже, когда я просто хочу сохранить значение $r:

# wrapped in eval just to make sure that $r is not overwritten by (the call chain of) coolf
eval '
    coolf "$tmp" || return "$lerrno"'"
    return $r
"

На самом деле я часто использую описанный выше прием для сохранения статусов выхода из наборов циклов -, а также выполняю операции очистки,как в:

    done <&3
    eval "unset ret vals; exec 3<&-; return $?"
}

Или в случаях, аналогичных вышеизложенным, в качестве «отсрочки исполнения»:

    done
    # return boolean set by loop while also unsetting it
    eval "unset ok; ${ok:-false}"
}

Обратите внимание, что одно из подразумеваемых намерений обоих приведенных выше фрагментов состоит в том, чтобы не оставлять «артефактов» при выполнении функции, особенно когда функция предназначена для интерактивного запуска. В последнем случае я мог бы вместо этого сделать:

[ "${ok:-false}" = false ] && { unset ok; return 1; } || { unset ok; return 0; }

но выглядит довольно грубо для меня.


Наконец, у меня было несколько случайных -случаев использования, когда я хотел/должен был изменить или расширить функцию лишь незначительно и на основе вызова, возможно, для небольших поведенческих изменений или для поддержки некоторых перехватов от вызывающей стороны. Подобно обратному вызову, но «встроенным» способом, который казался гораздо менее громоздким, особенно когда фрагменту ловушки требуется доступ к собственным $@аргументам функции. Естественно, такие фрагменты, передаваемые через переменные для последующегоeval-редактирования функцией, либо полностью статичны/сделаны вручную, либо сильно предварительно -контролируются/дезинфицируются.

6
28.04.2021, 22:57

Если бы вы сделали команду, которая берет шелл-код от пользователя, например, например. watchтогда evalиспользование такого кода кажется приемлемым.

1
28.04.2021, 22:57

Одно из реальных -жизненных употреблений eval, с которыми я столкнулся, используется в fluxbox.startfluxbox.dbus.diff.gz на Слэкваре. Выглядит так:

# Start DBUS session bus:
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
   eval $(dbus-launch --sh-syntax --exit-with-session)
fi

И хотя такое использование evalне было протестировано миллионами (Slackware не очень распространен )он делает свое дело. Тем не менее, я бы сделал все возможное чтобы избежать evalв моих сценариях оболочки. Если бы я чувствовал, что мне это нужно, для пример для реализации массивов или выполнения косвенных переменных, которые я бы переключиться на Bash, и если бы я все еще чувствовал, что мне это нужно, я бы переосмыслил сценарий дизайн или переключиться на совершенно другой язык.

4
28.04.2021, 22:57

Один пример, где я видел использование eval, находится в Модулях среды .

Там функция bash создается как оболочка вокруг «настоящей» программы:

module() { eval `/usr/bin/modulecmd bash $*`; }

Затем, когда я хочу загрузить модуль, скажем, gcc/7.2.0, я набираю

module load gcc/7.2.0

и usr/bin/modulecmdсчитывают среду и возвращают новую среду с расширенными путями:

CPP_INCLUDE_PATH=/nfs/modules/gcc/7.2.0/include:... ;export CPP_INCLUDE_PATH;
C_INCLUDE_PATH=/nfs/modules/gcc/7.2.0/include:... ;export C_INCLUDE_PATH;
LD_LIBRARY_PATH=/nfs/modules/gcc/7.2.0/lib:... ;export LD_LIBRARY_PATH;
LIBRARY_PATH=/nfs/modules/gcc/7.2.0/lib:... ;export LIBRARY_PATH;
...

Затем они evalредактируются функцией module, так что мое окружение изменяется.

Также pyenv использует evalаналогичным образом; программа создает код для изменения среды, и он evalредактируется функцией bash, чтобы изменить среду.

3
28.04.2021, 22:57

В итоге я использую 'eval' во многих моих сценариях установки и сборки, когда я проверяю файловую систему. Это включает в себя такие вещи, как плохие umasks, mkdir не соответствует POSIX или какие-то условия гонки. В конце концов, я делаю деревья и проверяю эти условия по мере продвижения. Вот фрагмент одного из моих скриптов:

if test -n "$prefixes"; then
    # Don't fail if two instances are running concurrently.
    (umask $mkdir_umask &&
     eval "\$doit_exec \$mkdirprog $prefixes") ||
      test -d "$dstdir" || exit 1
    mkdir_used=true
 fi

Вероятно, есть лучшие способы справиться с этим, но люди, которые лучше меня разбираются в сценариях, еще не ругали меня за это.

1
28.04.2021, 22:57

Теги

Похожие вопросы