Гид по технологиям

Как безопасно и эффективно использовать eval в Bash

8 min read Bash Обновлено 20 Nov 2025
Как безопасно и эффективно использовать eval в Bash
Как безопасно и эффективно использовать eval в Bash

Linux ноутбук с отображённой командной строкой bash

Быстрые ссылки

  • We Need To Talk About eval

  • First Steps With eval

  • Using Variables in the Command String

  • Accessing Variables Inside Variables

  • Using Dynamically Created Variables

  • Using eval With Arrays

  • It’s Not All Bad

Мы должны поговорить об eval

Команда eval имеет плохую репутацию — и не без оснований. eval собирает значения из одной или нескольких переменных, объединяет их в строку команды и затем выполняет эту команду в текущем шелле. Это делает eval полезным, когда содержание команды формируется динамически во время выполнения скрипта. Однако, если строка, передаваемая в eval, происходит из внешнего источника (ввод пользователя, API, HTTP-запросы и т. п.), существует риск выполнения вредоносных инструкций.

Важно: не используйте eval со строками, которые поступают из ненадёжных источников, если вы не проверяете и не фильтруете их.

Первые шаги с eval

eval — встроенная команда Bash. Если Bash установлен, eval присутствует.

Поведение простое: eval конкатенирует свои параметры в одну строку (разделённую одиночным пробелом), затем передаёт результирующую строку обратно интерпретатору для выполнения.

Пример: создадим переменную wordcount со строкой команды.

wordcount="wc -w raw-notes.md"

Вызов:

eval "$wordcount"

Команда выполняется в текущем шелле, а не в подсистеме (subshell). Это поведение важно, потому что изменения окружения (например, экспорт переменных) сохраняются в текущем процессе.

Пример, демонстрирующий, что eval может задать переменные в текущем шелле. Файл variables.txt содержит:

first=How-To
second=Geek

Выполнение:

cat variables.txt
eval "$(cat variables.txt)"
echo $first $second

Доступ к переменным, установленных eval в текущем шелле

Поскольку eval выполняет код в текущем шелле, переменные, которые он устанавливает, доступны после его выполнения. Важно: если eval вызывается внутри скрипта, то «текущий шелл» — это подсчел оболочки-скрипта, а не интерактивная оболочка, которая запустила скрипт.

Использование переменных внутри строки команды

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

num1=10
num2=7
add="`expr $num1 + $num2`"
show="echo"

eval $show $add

Использование переменных в строке команды

Здесь выражение expr вычисляется и подставляется в строку, затем eval выполняет команду echo с результатом. Обратите внимание на порядок подстановки: сначала Shell подставляет значения переменных в строку, затем eval соберёт и выполнит итоговую команду.

Обращение к переменным по имени переменной

Иногда в одной переменной хранится имя другой переменной. eval позволяет обратиться к значению переменной по имени, хранящемуся в другой переменной. Пример скрипта assign.sh:

#!/bin/bash

title="How-To Geek"

webpage=title

command="echo"

eval $command \${$webpage}

Сделайте исполняемым:

chmod +x assign.sh

Запуск:

./assign.sh

Вы увидите значение title, хотя eval используется с переменной webpage. Конструкция \${$webpage} заставляет eval сначала подставить имя переменной (title), а затем обратиться к её значению.

Динамическое создание переменных

eval удобно применять, когда нужно создавать переменные с именными шаблонами во время выполнения цикла. Пример loop.sh:

#!/bin/bash

total=0

label="Looping complete. Total:"

for n in {1..10}
do
  eval x$n=$n
  echo "Loop" $x$n
  ((total+=$x$n))
done

echo $x1 $x2 $x3 $x4 $x5 $x6 $x7 $x8 $x9 $x10

echo $label $total

Использование eval для динамического создания переменных

Здесь eval создаёт переменные x1..x10 и устанавливает их значения. После цикла эти переменные доступны по именам, потому что они созданы в текущем контексте.

Использование eval с массивами

Иногда нужно собрать имена файлов или ресурсов в массив и затем выполнять команду с каждым элементом. Ниже пример clear-logs.sh (упрощённо):

#!/bin/bash

declare -a logfiles

filecount=0

rm_string="echo"

function create_logfile() {
  ((++filecount))
  filename=$(date +"%Y-%m-%d_%H-%M-%S").log
  logfiles[$filecount]=$filename
  echo $filecount "Created" ${logfiles[$filecount]}
}

create_logfile
sleep 3
create_logfile
sleep 3
create_logfile
sleep 3
create_logfile

for ((file=1; file<=$filecount; file++))
do
  eval $rm_string ${logfiles[$file]} "deleted..."
  logfiles[$file]=""
done

Удаление файлов, имена которых хранятся в массиве

В реальном сценарии rm_string содержал бы rm -f, но в демонстрационных целях там echo, чтобы не удалять файлы случайно.

Eval — не так уж плох

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

Важно: никогда не выполняйте eval на входе от пользователей или из сетевых источников без строгой проверки.

Когда eval НЕ подходит

  • Когда строка с командой приходит из инпута пользователя, HTTP-запроса, результата непроверенного API.
  • Когда можно использовать встроенную функциональность Bash (см. раздел «Альтернативы»).
  • В сценариях отказоустойчивости и безопасности, где минимизация поверхности атаки важнее гибкости.

Альтернативы eval (практические приёмы)

Перед тем как использовать eval, рассмотрите эти альтернативы:

  • Косвенная подстановка ${!var} для обращения по имени переменной: если var=”NAME” и NAME=42, то echo ${!var} выведет 42.

  • Командная подстановка $(…) вместо подстановки строк в eval. Она безопаснее и проще для чтения.

  • printf -v varname “%s” “$value” для безопасной записи значения в переменную без выполнения.

  • declare -n ref=name для создания ссылки (nameref) на переменную (Bash 4.3+). Это безопасная и удобная альтернатива динамическим переменным.

  • Массивы и ассоциативные массивы (declare -A) для явного сопоставления ключ→значение вместо создания переменных x1..xN.

  • case/if/while с жёстко заданными шаблонами для управления потоком, вместо генерации кода строкой.

Примеры преобразований:

  • Вместо eval x$n=$n используйте:
declare -n ref=x$n
ref=$n

(Работает в Bash с поддержкой nameref.)

  • Вместо eval $show $add используйте:
echo "$add"

если show всегда равен “echo”; избегайте динамики, если она не нужна.

Модель мышления для использования eval

Простейшая эвристика — три вопроса перед использованием eval:

  1. Источник строки: полностью локальный, контролируемый кодом? Если нет — не используйте.
  2. Есть ли безопасная альтернатива (parameter expansion, nameref, массив)? Если да — используйте её.
  3. Можно ли сузить набор допустимых действий (белый список команд)? Если нет — откажитесь.

Если на все три вопроса ответы положительные (локально формируется строка, альтернативы непрактичны, и есть жёсткая валидация), тогда применение eval обосновано.

Практическое руководство и чеклист по безопасности

Важно: этот чеклист поможет минимизировать риски, но не отменяет здравого смысла и ревью кода.

Чеклист разработчика перед использованием eval:

  • Источник строки полностью контролируется внутри скрипта.
  • Если часть строки приходит извне — реализована строгая фильтрация и белый список.
  • Использованы кавычки, чтобы избежать непреднамеренной word-splitting и globbing.
  • По возможности заменён на declare -n, ${!var}, printf -v или массивы.
  • Добавлены unit-тесты/приёмочные тесты, проверяющие граничные случаи.
  • Код покрыт ревью секьюрити-специалиста.

Чеклист для ревью секьюрити:

  • Проанализирован источник входных данных.
  • Протоколированы возможные атаки (инъекции команд, path traversal и т. п.).
  • Проверен список допустимых команд и аргументов.
  • Проверены последствия выполнения в контексте прав (привилегии, chroot, контейнер).

Чеклист для DevOps перед деплоем:

  • Проверена настройка окружения (set -u, set -o pipefail, umask).
  • Скрипт выполняется в ограниченном окружении при необходимости (контейнер, non-root).
  • Логи и мониторинг настроены на отслеживание аномальной активности.

Шаблон SOP по замене eval

  1. Найдите место в коде, где используется eval.
  2. Определите источник данных, подставляемых в eval.
  3. Попробуйте заменить на одну из альтернатив (nameref, ${!var}, массив, printf -v).
  4. Если альтернатива невозможна, ограничьте допустимые команды белым списком и валидируйте аргументы.
  5. Добавьте unit-тесты, покрывающие как ожидаемые, так и вредоносные входные данные.
  6. Проведите ревью безопасности и прогоните сценарии в staging.
  7. Деплойте с мониторингом и откатом при аномалиях.

Примеры шаблонов и тест-кейсы

Тесты для сценария с динамическими переменными:

  • Позитивный кейс: n от 1 до 3, проверить, что x1..x3 заданы корректно.
  • Негативный кейс: если n содержит нечисловые символы, скрипт должен отказать (валидировать регуляркой ^[0-9]+$).
  • Граничный кейс: n слишком велик (например, >1000) — ограничить длину и количество создаваемых переменных.

Тесты для удаления логов:

  • Позитивный: скрипт удаляет только файлы, имена которых были сгенерированы функцией create_logfile.
  • Негативный: если внутри массива будет путь типа “/etc/passwd”, скрипт должен игнорировать такие элементы или логировать и пропускать.

Практические приёмы защиты и жёсткие правила

  • Кавычки: всегда кавычьте значения, когда это возможно. eval “…” лучше, чем eval … без кавычек.
  • Белые списки: ограничьте набор допустимых подкоманд (например, только echo, rsync, tar) и проверяйте аргументы.
  • Ограничение окружения: запустите скрипт с минимальными правами или в контейнере, чтобы минимизировать ущерб.
  • Логирование и аудит: записывайте, какие команды были сгенерированы и выполнены (в неинвазивном виде).
  • set -u и set -o pipefail: включите флаги безопасности в скриптах для раннего выхода при ошибках.

Когда eval оправдан

  • Генерация конфигурационных команд, где шаблон и набор параметров полностью контролируются программой.
  • Сценарии программной инициализации, где нужно выполнить набор команд, сформированных из заранее известных фрагментов.
  • Небольшие, чётко ограниченные утилиты и скрипты, которые не принимают ввод извне и проходят ревью.

Примеры неудач и антиобразцы

Пример опасного кода:

user_cmd="$1"
eval $user_cmd

Если $1 поступает от пользователя, это прямой путь к исполнению произвольных команд. Никогда так не делайте.

Лучше:

case "$1" in
  start|stop|status)
    cmd=$1
    ;;
  *)
    echo "Unknown action"; exit 2
    ;;
esac

# безопасный вызов
if [ "$cmd" = "start" ]; then
  systemctl start myservice
fi

Краткая методология принятия решения

  1. Определите, почему нужен eval — какую проблему он решает.
  2. Сопоставьте риски (источник входных данных, привилегии) и пользу.
  3. Если риск приемлем и есть строгая валидация — используйте eval с минимально необходимыми правами.
  4. Иначе — рефакторинг на альтернативы.

1-строчный глоссарий

  • eval: выполняет скомбинированную строку как команду в текущем шелле.
  • nameref (declare -n): ссылка на переменную по имени.
  • ${!var}: косвенная подстановка по имени переменной.
  • printf -v: безопасная запись в переменную без выполнения.

Итог

eval — это мощный инструмент для динамического выполнения команд в Bash. При корректном использовании он упрощает определённые задачи, но при неосторожном применении открывает возможность для инъекций и уязвимостей. Всегда проверяйте источник строки, предпочитайте альтернативы, и применяйте строгие меры контроля, если eval всё же необходим.

Ключевые моменты:

  • eval выполняет строки в текущем шелле.
  • Не используйте eval на данных из внешних источников без валидации.
  • Часто существуют безопасные альтернативы: ${!var}, declare -n, printf -v, массивы.
  • Пройдите чеклист безопасности, добавьте тесты и ревью перед деплоем.

Важно: используйте eval осмотрительно и только там, где преимущества перевешивают риски.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как устроить идеальную вечеринку для просмотра ТВ
Развлечения

Как устроить идеальную вечеринку для просмотра ТВ

Как распаковать несколько RAR‑файлов сразу
Инструменты

Как распаковать несколько RAR‑файлов сразу

Приватный просмотр в Linux: как и зачем
Приватность

Приватный просмотр в Linux: как и зачем

Windows 11 не видит iPod — способы исправить
Руководство

Windows 11 не видит iPod — способы исправить

PS5: как настроить игровые пресеты
Консоли

PS5: как настроить игровые пресеты

Как переключить камеру в Omegle на iPhone и Android
Руководство

Как переключить камеру в Omegle на iPhone и Android