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

Как безопасно и эффективно использовать 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
Автор
Редакция

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

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство