Как я могу заключить переменную в строку, чтобы избежать разделения слов?

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 . Таким образом, его содержание интерпретируется как код bash!

Пробелы в них приведут к передаче нескольких аргументов cdне из-за разделения слов , а потому, что они будут проанализированы как несколько токенов в синтаксисе оболочки. Со значениемbye;reboot , это перезагрузит! ¹

Здесь вы бы хотели:

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

(где вы передаете содержимое в $myvarкачестве первого аргумента этого встроенного скрипта; обратите внимание, как $myvarи$1 были процитированы для их соответствующей оболочки, чтобы предотвратить расщепление IFS-слов (и глобализация)).

Или:

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

¹ Строго говоря, для значений $myvarlike $(seq 10)(в буквальном смысле слова), конечно, произойдет разделение слов при расширении этой подстановки команд bash оболочкой, начинающейся какroot

Стефан Шазелас
источник
4

Есть новая (bash 4.4) магия цитирования, которая позволяет вам более прямо делать то, что вы хотите. В зависимости от более широкого контекста, использование одного из других методов может быть лучше, но оно работает в этом ограниченном контексте.

sudo bash -c "cd ${myvar@Q}; pwd"
Сет Робертсон
источник
4

Если вы не можете доверять содержимому $myvar, единственный разумный подход - использовать GNU printfдля создания его экранированной версии:

#!/bin/bash

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

Как printfнаписано на странице руководства :

%q

ARGUMENT печатается в формате, который можно использовать в качестве ввода для оболочки, избегая непечатаемых символов с предложенным $''синтаксисом POSIX .

Тоби Спейт
источник
Смотрите мой ответ для портативной версии этого.
R .. GitHub ОСТАНОВИТСЯ, ЧТОБЫ ЛЬДИТЬ
GNU printfбудет вызываться там только в системах GNU, и если $(printf '%q' "$myvar")он запускается в оболочке, где printfон не встроен. Большинство снарядов уже построены printf. Не все поддерживают %qхотя, и когда они это делают, они цитируют вещи в формате, поддерживаемом соответствующей оболочкой, который может не совпадать с понимаемым bash. На самом деле, bashs 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")"
R .. GitHub ОСТАНОВИТЬ, ПОМОГАЯ ЛЕД
источник
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:

$ 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\""
cunninghamp3
источник
2
По-прежнему уязвимость, связанная с внедрением команд, например, для значений $myvarlike $(reboot)или"; reboot; #
Стефан Шазелас
1
использование sudo bash -c "cd -P -- '$myvar'"ограничило бы проблему только значениями, $myvar которые содержат одинарные кавычки, что было бы легче дезинфицировать.
Стефан Шазелас