Какие символы должны быть экранированы при использовании Bash?

206

Есть ли исчерпывающий список символов, которые нужно экранировать в Bash? Это можно проверить только с sed?

В частности, я проверял, %нужно ли бежать или нет. Я попытался

echo "h%h" | sed 's/%/i/g'

и работал нормально, без побега %. Значит ли это, что %не нужно бежать? Был ли это хороший способ проверить необходимость?

И более общий: они одни и те же символы , чтобы бежать в shellи bash?

Федорки "ТАК прекратить вредить"
источник
4
В общем, если вы заботитесь, вы делаете это неправильно. Обработка данных никогда не должна включать в себя прохождение через процесс синтаксического анализа и оценки, используемого для кода, что позволяет избежать спора. Это очень похоже на лучшие практики для SQL - где правильно использовать переменные связывания, а неправильное - пытаться «санировать» данные, введенные с помощью подстановок строк.
Чарльз Даффи
Связано со stackoverflow.com/questions/2854655/…
skywinder
8
@CharlesDuffy Да, но иногда то, что движок подготовленных операторов делает с бэкэндом, просто ускользает от вещей. Так «делает это неправильно», потому что они избегают пользовательских комментариев, прежде чем отобразить их в браузере? Нет, они мешают XSS. Не заботясь вообще, делает это неправильно.
Парфянский выстрел
@ParthianShot: если обработчик подготовленных операторов не удерживает данные вне кода, люди, которые их написали, должны быть застрелены. Да, я знаю, что проводной протокол MySQL реализован таким образом; мое заявление остается в силе.
Чарльз Даффи
@CharlesDuffy И я хочу сказать, что иногда вы можете сделать так, чтобы что-то работало безопасно, используя цепочку инструментов, которая заставляла бы пуристов съежиться, или потратила в восемь раз больше времени и усилий на то, чтобы сделать это красиво, - и все еще стоит.
Парфянский выстрел

Ответы:

282

Есть два простых и безопасных правила, которые работают не только в, shно и в bash.

1. Поместите всю строку в одинарные кавычки

Это работает для всех символов, кроме одной кавычки. Чтобы избежать одиночной кавычки, закройте перед ней кавычку, вставьте одинарную кавычку и снова откройте кавычку.

'I'\''m a s@fe $tring which ends in newline
'

команда sed: sed -e "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"

2. Избегайте каждого символа с обратной косой чертой

Это работает для всех персонажей, кроме новой строки. Для символов новой строки используйте одинарные или двойные кавычки. Пустые строки все еще должны быть обработаны - заменить на""

\I\'\m\ \a\ \s\@\f\e\ \$\t\r\i\n\g\ \w\h\i\c\h\ \e\n\d\s\ \i\n\ \n\e\w\l\i\n\e"
"

СЕПГ команда: sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2b. Более читаемая версия 2

Есть простой безопасный набор символов, например [a-zA-Z0-9,._+:@%/-], который можно оставить без экранирования, чтобы сделать его более читабельным

I\'m\ a\ s@fe\ \$tring\ which\ ends\ in\ newline"
"

СЕПГ команда: LC_ALL=C sed -e 's/[^a-zA-Z0-9,._+@%/-]/\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.


Обратите внимание, что в программе sed невозможно узнать, заканчивается ли последняя строка ввода байтом новой строки (кроме случаев, когда он пуст). Вот почему обе приведенные выше команды sed предполагают, что это не так. Вы можете добавить цитируемую новую строку вручную.

Обратите внимание, что переменные оболочки определены только для текста в смысле POSIX. Обработка двоичных данных не определена. Для реализаций, которые имеют значение, двоичный код работает за исключением байтов NUL (поскольку переменные реализованы с помощью строк C и предназначены для использования в качестве строк C, а именно аргументов программы), но вам следует переключиться на «двоичный» языковой стандарт, такой как latin1 ,


(Вы можете легко проверить правила, прочитав спецификацию POSIX для sh. Для bash обратитесь к справочному руководству, связанному с @AustinPhillips)

Джо Со
источник
Примечание: хороший вариант № 1 можно увидеть здесь: github.com/scop/bash-completion/blob/… . Не требует запуска sed, но требует bash.
JWD
4
Примечание для любого другого (как я!), Который изо всех сил пытается заставить их работать .... похоже, что вкус sed, который вы получаете в OSX, не запускает эти команды sed должным образом. Они прекрасно работают на Linux, хотя!
Далан
@dalelane: не могу проверить здесь. Пожалуйста, измените, когда у вас есть версия, которая работает на обоих.
Джо Со
Кажется, вы пропустили, должна ли строка начинаться с '-' (минус), или это относится только к именам файлов? - в последнем случае требуется знак «./» впереди.
Слэшмаис
Я не уверен, что ты имеешь в виду. С этими командами sed входная строка берется из stdin.
Джо Со
59

формат, который можно использовать в качестве ввода оболочки

Для такого запроса существует специальная printf директива формата ( %q):

printf [-v var] формат [аргументы]

 %q     causes printf to output the corresponding argument
        in a format that can be reused as shell input.

Некоторые образцы:

read foo
Hello world
printf "%q\n" "$foo"
Hello\ world

printf "%q\n" $'Hello world!\n'
$'Hello world!\n'

Это можно использовать и через переменные:

printf -v var "%q" "$foo
"
echo "$var"
$'Hello world\n'

Быстрая проверка со всеми (128) байтами ascii:

Обратите внимание, что все байты от 128 до 255 должны быть экранированы.

for i in {0..127} ;do
    printf -v var \\%o $i
    printf -v var $var
    printf -v res "%q" "$var"
    esc=E
    [ "$var" = "$res" ] && esc=-
    printf "%02X %s %-7s\n" $i $esc "$res"
done |
    column

Это должно сделать что-то вроде:

00 E ''         1A E $'\032'    34 - 4          4E - N          68 - h      
01 E $'\001'    1B E $'\E'      35 - 5          4F - O          69 - i      
02 E $'\002'    1C E $'\034'    36 - 6          50 - P          6A - j      
03 E $'\003'    1D E $'\035'    37 - 7          51 - Q          6B - k      
04 E $'\004'    1E E $'\036'    38 - 8          52 - R          6C - l      
05 E $'\005'    1F E $'\037'    39 - 9          53 - S          6D - m      
06 E $'\006'    20 E \          3A - :          54 - T          6E - n      
07 E $'\a'      21 E \!         3B E \;         55 - U          6F - o      
08 E $'\b'      22 E \"         3C E \<         56 - V          70 - p      
09 E $'\t'      23 E \#         3D - =          57 - W          71 - q      
0A E $'\n'      24 E \$         3E E \>         58 - X          72 - r      
0B E $'\v'      25 - %          3F E \?         59 - Y          73 - s      
0C E $'\f'      26 E \&         40 - @          5A - Z          74 - t      
0D E $'\r'      27 E \'         41 - A          5B E \[         75 - u      
0E E $'\016'    28 E \(         42 - B          5C E \\         76 - v      
0F E $'\017'    29 E \)         43 - C          5D E \]         77 - w      
10 E $'\020'    2A E \*         44 - D          5E E \^         78 - x      
11 E $'\021'    2B - +          45 - E          5F - _          79 - y      
12 E $'\022'    2C E \,         46 - F          60 E \`         7A - z      
13 E $'\023'    2D - -          47 - G          61 - a          7B E \{     
14 E $'\024'    2E - .          48 - H          62 - b          7C E \|     
15 E $'\025'    2F - /          49 - I          63 - c          7D E \}     
16 E $'\026'    30 - 0          4A - J          64 - d          7E E \~     
17 E $'\027'    31 - 1          4B - K          65 - e          7F E $'\177'
18 E $'\030'    32 - 2          4C - L          66 - f      
19 E $'\031'    33 - 3          4D - M          67 - g      

Где первое поле является шестнадцатеричным значением байта, второе содержит, Eесли символ должен быть экранирован, а третье поле показывает экранированное представление символа.

Почему ,?

Вы могли видеть некоторые символы, которые не всегда нужно экранировать, например ,, }и {.

Так что не всегда , но когда - то :

echo test 1, 2, 3 and 4,5.
test 1, 2, 3 and 4,5.

или

echo test { 1, 2, 3 }
test { 1, 2, 3 }

но все равно

echo test{1,2,3}
test1 test2 test3

echo test\ {1,2,3}
test 1 test 2 test 3

echo test\ {\ 1,\ 2,\ 3\ }
test  1 test  2 test  3

echo test\ {\ 1\,\ 2,\ 3\ }
test  1, 2 test  3 
Ф. Хаури
источник
Проблема заключается в том, что при вызове pritnf через bash / sh строка должна быть сначала
очищена от
1
@ThorSummoner, если вы не передадите строку как буквальный аргумент в оболочку из другого языка (где вы, вероятно, уже знаете, как заключать в кавычки). В Python: subprocess.Popen(['bash', '-c', 'printf "%q\0" "$@"', '_', arbitrary_string], stdin=subprocess.PIPE, stdout=subprocess.PIPE).communicate()предоставит вам правильную версию в кавычках arbitrary_string.
Чарльз Даффи
1
FYI bash's %qбыл сломан в течение долгого времени - если мой разум хорошо мне служит, ошибка была исправлена ​​(но, возможно, все еще не исправлена) в 2013 году после того, как была сломана в течение ~ 10 лет. Так что не надейся на это.
Джо Со
@CharlesDuffy Конечно, как только вы окажетесь на земле Python, shlex.quote()(> = 3.3, pipes.quote()- недокументированный - для более старых версий) также выполнит работу и создаст более читаемую версию (добавляя кавычки и экранируя, если необходимо) большинства строк, без необходимости порождать раковину.
Томас Перл
1
Спасибо, чтобы добавить специальные заметки о ,. Я был удивлен, узнав, что встроенный Bash printf -- %q ','дает \,, но /usr/bin/printf -- %q ','дает ,(не экранированный). То же самое для других символов: {, |, }, ~.
Кевинарпе
34

Чтобы избавить кого-то от необходимости использовать RTFM ... в bash :

Ограждающие символы в двойных кавычках сохраняет буквальное значение всех символов в кавычках, за исключением $, `, \и, когда раскрывание истории включено, !.

... так что если вы избежите этого (и сама цитата, конечно), вы, вероятно, в порядке.

Если вы придерживаетесь более консервативного подхода «когда сомневаетесь, избегайте его», следует избегать использования вместо них символов со специальным значением, не избегая идентифицирующих символов (т. Е. Букв ASCII, цифр или «_»). Очень маловероятно, что они когда-либо (т.е. в какой-то странной оболочке POSIX-ish) будут иметь особое значение и, следовательно, должны быть экранированы.

Мэтью
источник
1
вот руководство цитированной выше: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk
Это короткий, приятный и в основном правильный ответ (+1 за это), но, возможно, еще лучше использовать одинарные кавычки - см. Мой более длинный ответ.
Джо Со
26

Используя эту print '%q' технику , мы можем запустить цикл, чтобы узнать, какие символы особенные:

#!/bin/bash
special=$'`!@#$%^&*()-_+={}|[]\\;\':",.<>?/ '
for ((i=0; i < ${#special}; i++)); do
    char="${special:i:1}"
    printf -v q_char '%q' "$char"
    if [[ "$char" != "$q_char" ]]; then
        printf 'Yes - character %s needs to be escaped\n' "$char"
    else
        printf 'No - character %s does not need to be escaped\n' "$char"
    fi
done | sort

Это дает такой вывод:

No, character % does not need to be escaped
No, character + does not need to be escaped
No, character - does not need to be escaped
No, character . does not need to be escaped
No, character / does not need to be escaped
No, character : does not need to be escaped
No, character = does not need to be escaped
No, character @ does not need to be escaped
No, character _ does not need to be escaped
Yes, character   needs to be escaped
Yes, character ! needs to be escaped
Yes, character " needs to be escaped
Yes, character # needs to be escaped
Yes, character $ needs to be escaped
Yes, character & needs to be escaped
Yes, character ' needs to be escaped
Yes, character ( needs to be escaped
Yes, character ) needs to be escaped
Yes, character * needs to be escaped
Yes, character , needs to be escaped
Yes, character ; needs to be escaped
Yes, character < needs to be escaped
Yes, character > needs to be escaped
Yes, character ? needs to be escaped
Yes, character [ needs to be escaped
Yes, character \ needs to be escaped
Yes, character ] needs to be escaped
Yes, character ^ needs to be escaped
Yes, character ` needs to be escaped
Yes, character { needs to be escaped
Yes, character | needs to be escaped
Yes, character } needs to be escaped

Некоторые результаты ,выглядят немного подозрительно. Было бы интересно получить отзывы @ CharlesDuffy по этому вопросу.

codeforester
источник
2
Вы можете прочитать ответ, чтобы ,выглядеть немного подозрительно в последнем абзаце моего ответа
Ф. Хаури
2
Имейте в виду, что %qон не знает, где внутри оболочки вы планируете использовать символ, поэтому он будет экранировать все символы, которые могут иметь особое значение в любом возможном контексте оболочки. ,само по себе не имеет особого значения для оболочки, но, как указал @FHauri в своем ответе, оно имеет особое значение в {...}расширении фигурных скобок: gnu.org/savannah-checkouts/gnu/bash/manual/… Это как! который также требует расширения только в определенных ситуациях, а не в целом: echo Hello World!работает просто отлично, но не echo test!testполучится.
Меки
18

Символы, которые нужно экранировать, отличаются в Bourne или POSIX от Bash. Обычно (очень) Bash является надмножеством этих оболочек, поэтому все, что вы сбежали, shellдолжно быть сброшено в Bash.

Хорошим общим правилом было бы «если сомневаешься, избегай его». Но экранирование некоторых символов придает им особое значение, например \n. Они перечислены на man bashстраницах под Quotingи echo.

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

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

Тот , который поймал меня есть !. Это специальный символ (расширение истории) в Bash (и csh), но не в оболочке Korn. Даже echo "Hello world!"дает проблемы. Использование одинарных кавычек, как обычно, удаляет особое значение.

cdarke
источник
1
Мне особенно нравится хорошее общее правило: совет «если сомневаешься, избегай его» . Тем не менее есть сомнения, достаточно ли sedхороша проверка с помощью , чтобы увидеть, нужно ли ее избежать. Спасибо за Ваш ответ!
Федорки 'ТАК прекрати вредить'
2
@fedorqui: проверять с помощью sedне нужно, вы можете проверить практически с чем угодно. sedэто не проблема, bashесть. Внутри одинарных кавычек нет специальных символов (кроме одинарных), вы даже не можете экранировать символы. sedКоманда должна обычно находиться внутри одинарных кавычек , потому что RE метасимволы имеют слишком много совпадений с метасимволами , чтобы быть безопасными. Исключением является встраивание переменных оболочки, что необходимо делать осторожно.
cdarke
5
Проверьте с echo. Если вы получаете то, что положили, от него не нужно бежать. :)
Марк Рид
6

Я предполагаю, что вы говорите о струнах bash. Существуют разные типы строк, которые имеют разные требования к экранированию. например. Строки в одинарных кавычках отличаются от строк в двойных кавычках.

Лучшим справочником является раздел цитирования руководства по bash.

Это объясняет, каких персонажей нужно убежать. Обратите внимание, что некоторым символам может потребоваться экранирование в зависимости от того, какие опции включены, например, расширение истории.

Остин Филлипс
источник
3
Таким образом, это подтверждает, что побег это такие джунгли без простого решения, придется проверять каждый случай. Спасибо!
Федорки "ТАК прекрати вред"
@fedorqui Как и на любом языке, существует ряд правил, которым нужно следовать. Для экранирования строки bash набор правил довольно мал, как описано в руководстве. Самая простая строка - это одинарные кавычки, так как ничего не нужно экранировать. Тем не менее, нет способа включить одну кавычку в одну строку в кавычках.
Остин Филлипс
@fedorqui. Это не джунгли. Сбежать вполне выполнимо. Смотрите мой новый пост.
Джо Со
@fedorqui Вы не можете использовать одинарную кавычку внутри строки в одинарных кавычках, но вы можете "экранировать" ее с помощью чего-то вроде: 'text' '' '' more text '
CR.
4

Я заметил, что bash автоматически экранирует некоторые символы при использовании автозаполнения.

Например, если у вас есть каталог с именем dir:A, bash автоматически завершитdir\:A

Используя это, я провел несколько экспериментов с использованием символов таблицы ASCII и вывел следующие списки:

Символы, которые удаляются через bash при автозаполнении : (включает пробел)

 !"$&'()*,:;<=>?@[\]^`{|}

Символы, которые bash не избежать :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Я исключил /, так как его нельзя использовать в именах каталогов)

юрий
источник
2
Если вы действительно хотите получить исчерпывающий список, я бы посоветовал посмотреть, какие символы printf %qизменяют, а какие не изменяют, если передаются в качестве аргумента - в идеале, проходя весь набор символов.
Чарльз Даффи
Есть случаи, когда даже со строкой апострофа вы можете захотеть экранировать буквы и цифры, чтобы получить специальные символы. Например: tr '\ n' '\ t', который переводит символы новой строки в символы табуляции.
Дик Гертен