Які символи потрібно уникати під час використання Bash?


206

Чи є якийсь вичерпний список персонажів, яких потрібно уникати в Bash? Чи можна це перевірити просто sed?

Зокрема, я перевіряв, чи %потрібно уникати чи ні. я намагався

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

і працював чудово, не тікаючи %. Це означає, %що не потрібно бігти? Це був хороший спосіб перевірити необхідність?

І ще загальніше: вони ті самі персонажі, куди втекти shellі bash?


4
Взагалі, якщо вам все одно, ви робите це неправильно. Обробка даних ніколи не повинна запускати їх за допомогою процесу розбору та оцінки, використовуваного для коду, що робить пробіг спірним. Це дуже близька паралель кращих практик для SQL - де правильна річ полягає у використанні змінних прив'язки, а неправильна річ - у спробі "санітизувати" дані, введені за допомогою рядкових підстановок.
Чарльз Даффі,


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 : sed -e 's/./\\&/g; 1{$s/^$/""/}; 1!s/^/"/; $!s/$/"/'.

2б. Більш читана версія 2

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

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

команда sed: 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

@dalelane: Неможливо перевірити тут. Відредагуйте, коли у вас є версія, яка працює на обох.
Jo So

Здається, що ви пропустили, якщо рядок починається з "-" (мінус), чи це стосується лише імен файлів? - в останньому випадку потрібно "./" спереду.
slashmais

Я не впевнений, що ти маєш на увазі. За допомогою цих команд sed вхідний рядок береться з stdin.
Jo So

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) байтами:

Зауважте, що всі байти від 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 років. Тому не покладайтеся на це.
Jo So

@CharlesDuffy Звичайно, щойно ви перебуваєте на землі Python, shlex.quote()(> = 3.3, pipes.quote()- недокументований - для старих версій) також зробить роботу та створить більш читану людиною версію (додавання лапок та уникнення необхідності) більшості рядків, без необхідності нерестувати шкаралупу.
Томас Перл

1
Дякуємо, що додавали спеціальні примітки про ,. Я був здивований, дізнавшись, що вбудований Bash printf -- %q ','дає \,, але /usr/bin/printf -- %q ','дає ,(un-escape). Те ж саме для інших символів: {, |, }, ~.
кевінарпе

34

Щоб врятувати когось іншого від необхідності використовувати RTFM ... in bash :

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

... тож якщо ти уникнеш цих (і самої цитати, звичайно), ти, мабуть, добре.

Якщо ви скористаєтеся більш консервативним підходом «коли сумніваєтесь, уникайте цього», слід уникати отримання замість них символів із спеціальним значенням, не уникаючи символів ідентифікатора (тобто букви ASCII, цифри або «_»). Це дуже малоймовірно, що вони коли-небудь (тобто в якійсь дивній оболонці POSIX-ish) мали особливе значення, і тому їх потрібно уникати.


1
ось цитований вище посібник: gnu.org/software/bash/manual/html_node/Double-Quotes.html
code_monk

Це коротка, мила і здебільшого правильна відповідь (+1 для цього), але, можливо, ще краще використовувати одинарні цитати - дивіться мою довшу відповідь.
Jo So

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 про це.


2
Ви можете прочитати відповідь, щоб ,виглядати трохи підозрілим на останньому абзаці моєї відповіді
Ф. Хаурі

2
Майте на увазі, що %qне знаєте, де в оболонці ви плануєте використовувати цей символ, тому він уникне всіх символів, які можуть мати особливе значення в будь-якому можливому контексті оболонки. ,вона не має особливого значення для оболонки, але як зазначає @ F.Hauri у своїй відповіді, вона має особливе значення в рамках {...}розширення дужки: gnu.org/savannah-checkouts/gnu/bash/manual/… Це як! що також вимагає розширення лише в конкретних ситуаціях, а не в цілому: echo Hello World!працює просто чудово, але echo test!testне вдасться.
Мецьки

18

Символи, які потребують втечі, відрізняються в оболонці Bourne або POSIX, ніж Bash. Взагалі (дуже) Баш - це сукупність цих снарядів, тому все, що вам вдасться втектиshell повинне уникнути в Баші.

Гарним загальним правилом було б "якщо сумніваєтесь, уникайте цього". Але втеча від деяких персонажів надає їм особливого значення, як \n. Вони перераховані на man bashсторінках під Quotingі echo.

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

Сторінки чоловіків перераховують їх десь, але не в одному місці. Вивчіть мову, саме в цьому можна переконатися.

Те, що мене наздогнало, це !. Це особливий символ (розширення історії) в Bash (і csh), але не в оболонці Korn. Навіть echo "Hello world!"створює проблеми. Використання одинарних лапок, як зазвичай, знімає особливе значення.


1
Мені особливо подобається приємним загальним правилом була порада "якщо сумніваєтесь, уникайте цього" . Ви все ще сумніваєтесь, чи sedдостатньо хороша перевірка , щоб побачити, чи потрібно їй уникнути. Дякую за вашу відповідь!
fedorqui 'ТАК перестаньте шкодити'

2
@fedorqui: Перевірка з sedне потрібна, ви можете перевірити майже все. sedне проблема, bashє. Всередині одинарних лапок немає спеціальних символів (крім одинарних лапок), ви навіть не можете уникнути символів. sedКоманда повинна зазвичай знаходитися всередині одинарних лапок , тому що RE метасимволу мають занадто багато збігів з метасимвол , щоб бути безпечними. Виняток - це коли вставляти змінні оболонки, що потрібно робити обережно.
cdarke

5
Перевірте echo. Якщо ви дістанете те, що вкладаєте, його не потрібно уникати. :)
Марк Рід

6

Я припускаю, що ви говорите про струнні баші. Існують різні типи рядків, які мають різний набір вимог до виходу. напр. Рядки одинарних лапок відрізняються від рядків з подвійним цитуванням.

Найкраща посилання - це розділ цитування керівництва з bash.

Це пояснює, яким персонажам потрібно втекти. Зауважте, що деяким символам може знадобитися біг залежно від того, які параметри включені, наприклад розширення історії.


3
Тож це підтверджує, що втеча - це такі джунглі без легкого рішення, доведеться перевіряти кожен випадок. Дякую!
fedorqui 'SO перестаньте шкодити'

@fedorqui Як і будь-яка мова, існує набір правил, яких слід дотримуватися. Для виходу з рядка bash набір правил є досить малим, як описано в посібнику. Найпростіший рядок у використанні - це окремі лапки, оскільки нічого не потрібно бігти. Однак немає можливості включити єдину цитату в одну котирувану рядок.
Остін Філіпс

@fedorqui. Це не джунглі. Втеча цілком можливо. Дивіться мою нову публікацію.
Jo So

@fedorqui Ви не можете використовувати одну цитату всередині одного котируваного рядка, але ви можете "уникнути" її за допомогою: "текст" "" "більше тексту"
CR.

4

Я помітив, що bash автоматично уникає деяких символів при використанні функції автоматичного завершення.

Наприклад, якщо у вас є ім'я з каталогом dir:A, bash автоматично заповнитьсяdir\:A

Використовуючи це, я провів деякі експерименти, використовуючи символи таблиці ASCII, і вивів такі списки:

Символи, які втрачаються під час автоматичного завершення : (включає пробіл)

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

Персонажі, які баш не уникають :

#%+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~

(Я виключив /, оскільки його не можна використовувати в іменах каталогу)


2
Якщо ви дійсно хотіли мати вичерпний список, я б запропонував переглянути, які символи printf %qроблять, а не змінювати, якщо вони передаються як аргумент - в ідеалі, пройшовши весь набір символів.
Чарльз Даффі

Є випадки, коли навіть із рядком апострофа ви можете уникати букв і цифр, щоб створити спеціальні символи. Наприклад: tr '\ n' '\ t', який переводить символи нового рядка в символи вкладки.
Дік Гертін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.