Привет^3
Давайте разберёмся, как символ - может создать реальные проблемы безопасности в автоматизации и скриптах.
Начнём с небольшой истории.
Ничего не предвещало беды
Была чудесная среда. Я пил вкусный кофе (настоятельно рекомендую инфьюз-обработку).
И понадобилось мне получить пути до файлов из командной строки.
Как это сделать?
Разбить строку по пробелу — кажется хорошей идеей.
Но вдруг я случайно нажму два пробела? Уже проблема.
В регулярных выражениях есть полезный метасимвол \s.
Он обозначает все пробельные символы: пробел, \t, \r, \f.
Звучит удобно.
Но дальше начинается интересное.
Мне нужно не только получить пути до файлов, но и открыть каждый из них.
И тут выясняется, что у команд есть флаги, которые начинаются с -.
А значит — такие аргументы интерпретируются не как файлы.
Чёртов дефис
Символ - может быть частью имени файла.
Более того — он может стоять в начале имени.
Например, файл с именем -file.

Первая мысль обычно такая:
«Да всё просто, экранируем через
\:touch \-file»

Но нет — это не работает.
А если взять имя в кавычки?

Тоже мимо.

А как тогда?
Я нашёл два рабочих варианта:
- использовать явный путь;
- использовать специальную конструкцию
--.
Разберём оба.
Полный (или относительный) путь
Во всех примерах выше мы передавали имя файла относительно текущей директории (cwd).
Если же указать путь явно (./-file или /path/to/-file), файл перестаёт интерпретироваться как флаг:

То же самое работает и с абсолютным путём:

Специальная конструкция --
В bash есть специальная конструкция, обозначающая конец флагов — --.
Все аргументы после -- больше не интерпретируются как опции:


А зачем это вообще нужно?
Для повседневной работы это просто неудобство.
А вот в автоматизации и security-инструментах такие мелочи легко превращаются в проблемы.
Если неправильно обрабатывать аргументы, можно:
- пропустить файл, имя которого начинается с
-; - или сделать что-то намного хуже.
Например, такой код:
for f in $(ls); do
rm $f
done
(да, for f in $(ls) — это отдельная боль, но сейчас не об этом)
Если в директории есть файл -rf, последствия очевидны.
Правильный вариант:
for f in $(ls); do
rm -- "$f"
done
Если вы парсите аргументы без –, вы уже пишете потенциально небезопасный код.