Как мне избежать символа подстановки / звездочки в bash?

136

Например:

me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR

и используя \escape-символ:

me$ FOO="BAR \* BAR"
me$ echo $FOO
BAR \* BAR

Я, очевидно, делаю что-то глупое.

Как мне получить вывод BAR * BAR?

andyuk
источник

Ответы:

139

Цитирование при настройке $FOOнедостаточно. Вам также нужно заключить в кавычки переменную:

me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR
finnw
источник
8
это загадочно, почему это? что происходит?
тофутим
Потому что переменные расширяются
Даниил
103

КОРОТКИЙ ОТВЕТ

Как уже говорили другие - вы всегда должны указывать переменные, чтобы избежать странного поведения. Поэтому используйте echo "$ foo" вместо echo $ foo .

ДОЛГО ОТВЕТ

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

Я вижу, в чём состоит ваше замешательство, потому что после запуска вашего первого примера вы, вероятно, подумали про себя, что оболочка явно делает:

  1. Расширение параметра
  2. Расширение имени файла

Итак, из вашего первого примера:

me$ FOO="BAR * BAR"
me$ echo $FOO

После расширения параметра эквивалентно:

me$ echo BAR * BAR

И после расширения имени файла эквивалентно:

me$ echo BAR file1 file2 file3 file4 BAR

И если вы просто наберете echo BAR * BARв командной строке, вы увидите, что они эквивалентны.

Так что вы, вероятно, подумали про себя: «если я уйду от *, я могу предотвратить расширение имени файла»

Итак, из вашего второго примера:

me$ FOO="BAR \* BAR"
me$ echo $FOO

После расширения параметра должно быть эквивалентно:

me$ echo BAR \* BAR

И после расширения имени файла должно быть эквивалентно:

me$ echo BAR \* BAR

И если вы попытаетесь набрать «echo BAR \ * BAR» непосредственно в командной строке, он действительно выведет «BAR * BAR», потому что расширение имени файла предотвращается экранированием.

Так почему же использование $ foo не работает?

Это происходит потому, что происходит третье расширение - удаление цитаты. Из bash ручное удаление цитаты есть:

После предыдущих расширений удаляются все вхождения без кавычек символов '\', '' 'и' "', которые не являются результатом одного из указанных выше расширений.

Итак, что происходит, когда вы вводите команду непосредственно в командной строке, escape-символ не является результатом предыдущего расширения, поэтому BASH удаляет его перед отправкой в ​​команду echo, но во 2-м примере "\ *" был результат предыдущего расширения параметра, поэтому он НЕ удаляется. В результате эхо получает «\ *», и это то, что он печатает.

Обратите внимание на разницу между первым примером: «*» не входит в символы, которые будут удалены при удалении цитаты.

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

Для полного объяснения расширений BASH обратитесь к:

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions

mithu
источник
1
Отличный ответ! Теперь я не чувствую, что задал такой тупой вопрос. :-)
андюк
1
Существуют ли какие-либо утилиты для экранирования специальных символов?
Джигар Джоши
«Вы всегда должны заключать переменные в кавычки, чтобы избежать странного поведения» - когда вы хотите использовать их в качестве строк
Angelo
См. Также stackoverflow.com/questions/10067266/…
tripleee
В то время как ответ от @finnw выше прямо отвечает на вопрос, этот ответ гораздо лучше объясняет причину этого, что намного помогает в экстраполяции того, как будет работать использование в наших собственных сценариях. Это тот ответ, который нам нужен, чтобы увидеть больше.
Николас Кумбс
54

Я добавлю немного к этой старой теме.

Обычно вы бы использовали

$ echo "$FOO"

Однако у меня были проблемы даже с этим синтаксисом. Рассмотрим следующий скрипт.

#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"

Эти *потребности должны быть переданы дословно curl, но возникают те же проблемы. Приведенный выше пример не будет работать (он будет расширен до имен файлов в текущем каталоге) и не будет работать \*. Вы также не можете $curl_optsзаключить в кавычки, потому что это будет признано как единственная (недействительная) опция curl.

curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

Поэтому я бы порекомендовал использовать bashпеременную, $GLOBIGNOREчтобы вообще предотвратить расширение имени файла, если он применяется к глобальному шаблону, или использовать set -fвстроенный флаг.

#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"  ## no filename expansion

Применяя к вашему первоначальному примеру:

me$ FOO="BAR * BAR"

me$ echo $FOO
BAR file1 file2 file3 file4 BAR

me$ set -f
me$ echo $FOO
BAR * BAR

me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR
AlexandreH
источник
4
Отличное объяснение, спасибо! Мой пример использования SELECT * FROM etc., это единственный способ, который работает.
Кнутоль
1
Спасибо за представление решения set -f!
hachre
5
FOO='BAR * BAR'
echo "$FOO"
tzot
источник
Это работает, но не обязательно менять одинарные кавычки в первой строке на двойные.
Finnw
1
Нет, это не так, но привычка использовать одинарные кавычки предпочтительнее, когда вы собираетесь включать специальные символы оболочки и не хотите никакой замены.
Цот
3

Возможно, стоит привыкнуть использовать printfвместо echoкомандной строки.

В этом примере это не дает большой выгоды, но может быть более полезным при более сложном выводе.

FOO="BAR * BAR"
printf %s "$FOO"
Дэйв Уэбб
источник
Почему в аду? printf - это отдельный процесс (по крайней мере, не встроенный в bash), и использование printf, которое вы демонстрируете, не имеет никакого преимущества по сравнению с echo.
ddaa
2
printf - это встроенный bash, и я сказал в своем ответе: «В этом примере это не очень полезно, но может быть более полезным при более сложном выводе.
Дэйв Уэбб,