як я можу процитувати розширення змінної всередині рядка, щоб уникнути розбиття слів?


9
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

У такому випадку, як я можу процитувати, $myvarщоб уникнути розбиття слів через пробіли у значенні myvar?


4
Як би ви не вирішили це, це все одно не зробить дуже багато. cdМає тільки ефект всередині bash -cоболонки.
Кусалаланда

Я наводжу лише мінімальний приклад лише для питання, це не робоче рішення моєї реальної проблеми, яка є unix.stackexchange.com/a/269080/674, плюс те, що в командному рядку, наданому sudo bash -cмені, є розширене розширення на ім'я шляху, яке може містити пробіли.
Тім

Відповіді:


13

У цьому коді немає розбиття слів (як у функції, яка розбиває змінні на розкладені без котирування), оскільки $myvarце не цитується.

Однак існує вразливість введення команд, $myvarяка розширюється перед тим, як перейти до неї bash. Тож його вміст трактується як баш-код!

Пробіли там спричинить передачу декількох аргументів cdне через розбиття слів , а тому, що вони будуть синтаксично проаналізовані як декілька лексем у синтаксисі оболонки. Зі значенням bye;reboot, що перезавантажиться! ¹

Тут вам потрібно:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(Де ви передаєте вміст в $myvarякості першого аргументу цього вбудованого скрипта, до відома , як і $myvarі $1були процитовані для їх відповідної оболонки , щоб запобігти МФС-софістику (і підстановку)).

Або:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(де ви передаєте вміст $myvarзмінної середовища).

Звичайно, нічого корисного ви не досягнете, запустивши лише cd цей вкладений сценарій (крім перевірки, чи rootможна cdтам). Імовірно, ви хочете, щоб цей сценарій cdтам, а потім зробити щось інше, як-от:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

Якщо намір полягав у тому, sudoщоб мати можливість cdпотрапляти в каталог, до якого ви інакше не маєте доступу, то це насправді не може працювати.

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

запустить інтерактив bashзі своїм поточним каталогом у $myvar. Але ця оболонка буде працювати як root.

Ви можете зробити:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

Щоб отримати непривілейований інтерактив bashіз поточним каталогом $myvar, але якщо у вас не було дозволів cdвходити до цього каталогу в першу чергу, ви не зможете робити нічого в цьому каталозі, навіть якщо це ваш поточний робочий каталог.

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

Виняток буде, якщо у вас є дозвіл на пошук до самого каталогу, але не до одного з компонентів каталогу його шляху:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

¹ строго кажучи, для значень на $myvarкшталт $(seq 10)(буквально) було б розбиття слів звичайно після розширення заміни цієї команди bash оболонкою, що починається якroot


4

Існує нова (баш 4.4) магія цитування, яка дозволяє вам безпосередньо безпосередньо робити те, що ви хочете. Залежно від більшого контексту використання однієї з інших методик може бути кращим, але це працює в цьому обмеженому контексті.

sudo bash -c "cd ${myvar@Q}; pwd"

4

Якщо ви не можете довіряти вмісту $myvar, єдиним розумним підходом є використання GNU printfдля створення випущеної версії його:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

Як printfговорить сторінка man:

%q

ARGUMENT друкується у форматі, який можна використовувати повторно як введення оболонки, уникаючи символів, що не надруковані, із запропонованим $''синтаксисом POSIX .


Дивіться мою відповідь на портативну версію цього.
R .. GitHub СТОП ДОПОМОГАЙТЕ

GNU printfбуде викликатися там лише в системах GNU, і якщо $(printf '%q' "$myvar")це запускається в оболонці, де printfне вбудовано. Більшість снарядів printfпобудовано в наш час. Не всі підтримують, %qхоча і коли вони роблять, вони цитують річ у форматі, підтримуваному відповідною оболонкою, яка не обов'язково може бути такою ж, як зрозуміла bash. На насправді, bash«s printfне може процитувати речі правильно для навіть самого для деяких значень $myvarв деяких місцях.
Стефан Шазелас

З bash-4.3принаймні, до сих пір ін'єкції команди вразливість з myvar=$'\xa3``reboot`\xa3`' в а zh_HK.big5hkscs локалі, наприклад.
Стефан Шазелас

3

Перш за все, як зауважили інші, ваша cdкоманда марна, оскільки це відбувається в контексті оболонки, яка негайно виходить. Але загалом питання має сенс. Вам потрібен спосіб оболонки цитувати довільну рядок , щоб вона могла бути використана в контексті, коли вона буде інтерпретуватися як рядок із цитатами оболонки. У мене є приклад цього на моїй сторінці з трюками :

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

За допомогою цієї функції ви можете:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"

3

Те, що пропонує Стефан Шазелас, безумовно, найкращий спосіб зробити це. Я просто надам відповідь про те, як безпечно цитувати синтаксис розширення параметрів на відміну від використання sedпідпроцесу, як у відповіді R ..

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

Вихід цього сценарію:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar

1

Так, є розділення слів.
Ну, технічно, розбиття командного рядка на bash, що аналізує рядок.

Давайте спочатку правильно почнемо цитувати:

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

bash -c "cd \"$myvar\""

Давайте підтвердимо, що відбувається (припускаючи myvar="/path to/my directory"):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

Як бачите, рядок шляху був розділений на пробіли. І:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

Не розщеплений.

Однак команда (як написано) небезпечна. Краще використання:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

Це захистить компакт-диск від файлів, які починаються з тире (-) або наступних посилань, що може призвести до проблем.

Це спрацює, якщо ви можете довіряти, що рядок у $myvar- це шлях.
Однак "введення коду" все ще можливо, оскільки ви "виконуєте рядок":

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

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

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

Як ви бачите вище, значення не інтерпретувались, а просто використовувались як "$1'.

Тепер ми можемо (безпечно) використовувати судо:

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

Якщо є кілька аргументів, ви можете використовувати:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"

-2

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

sudo bash -c "cd \"$myvar\""

2
І все-таки вразливість командної ін'єкції, як для значень, $myvarподібних $(reboot)або,"; reboot; #
Stéphane Chazelas

1
використання sudo bash -c "cd -P -- '$myvar'"обмежило б проблему лише значеннями, $myvar що містять символи одинарних цитат, які було б легше переосмислити.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.