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


136

Наприклад:

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

і використовуючи \символ втечі:

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

Я, очевидно, роблю щось дурне.

Як отримати вихід BAR * BAR?

Відповіді:


139

Цитування при встановленні $FOOнедостатньо. Вам також потрібно навести змінну посилання:

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

8
це таємниче, чому це? що відбувається?
tofutim

Тому що змінні розширюються
Даніель

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 видаляє її перед відправленням команді echo, але у другому прикладі "\ *" було результат попереднього розширення параметра, тому він НЕ видаляється. В результаті ехо отримує "\ *" і саме це друкує.

Зауважте, різниця між першим прикладом - "*" не входить до символів, які будуть видалені шляхом видалення цитата.

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

Повне пояснення розширень BASH дивіться на:

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


1
Чудова відповідь! Тепер я не відчуваю, що я задавав таке німе запитання. :-)
andyuk

1
Чи є якась утиліта, щоб уникнути особливих символів?
Jigar Joshi

"Ви завжди повинні цитувати змінні для запобігання дивної поведінки" - коли ви хочете використовувати їх як рядки
Angelo


Хоча відповідь від @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

4
Чудове пояснення, дякую! Моя скринька - SELECT * FROM etc.це єдиний спосіб, який працює.
кнутоле

1
Дякуємо, що представили рішення set -f!
хахре

5
FOO='BAR * BAR'
echo "$FOO"

Це працює, але не суттєво змінювати одиничні лапки в першому рядку на подвійні лапки.
finnw

1
Ні, це не так, але звичка використовувати одинарні лапки є кращою, коли ви збираєтеся включати спеціальні символи оболонки, і ви не хочете ніякої заміни.
тзот


3

Можливо, варто скористатися звичкою використовувати, printfа не echoкомандний рядок.

У цьому прикладі це не дає великої користі, але може бути кориснішим при більш складних результатах.

FOO="BAR * BAR"
printf %s "$FOO"

Чому в пеклі? printf - це окремий процес (принаймні, не вбудований в bash), і використання printf, яке ви демонструєте, не має користі над луною.
ddaa

2
printf - це вбудований баш, і я сказав у своїй відповіді "У цьому прикладі це не приносить великої користі, але може бути кориснішим при більш складних вихідних даних."
Дейв Вебб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.