Расширение (объединение) подстановочных знаков в строке, состоящей из частей в кавычках и без кавычек

#bash #shell #glob #quoting #shopt

#bash #оболочка #глобус #цитирование #shopt

Вопрос:

У меня есть строка в сценарии оболочки, которая выглядит следующим образом:

 java -jar "$dir/"*.jar
  

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

 Error: Unable to access jarfile [folder-name]/*.jar
  

Он принимает символ ‘*’ буквально, вместо того, чтобы выполнять замену, которую я хочу. Как мне это исправить?

РЕДАКТИРОВАТЬ: теперь он работает. У меня просто был неправильный префикс папки:/ Для тех, кому интересно, это правильный способ сделать это.

Комментарии:

1. Если .jar в определенном каталоге нет файла, заканчивающегося x на , то результатом попытки расширения x/*.jar будет x/*.jar , которого не существует. По крайней мере, по умолчанию in bash , похоже, работает таким образом в большинстве установок. bash имеет ряд опций (например failglob , nullglob , и несколько других) для изменения поведения по умолчанию, если это то, что вы хотите.

Ответ №1:

Вам просто нужно установить failglob :

 shopt -s failglob
  

чтобы избежать отображения литерала *.jar , когда в данной папке нет совпадений.

PS: это приведет к ошибке, если не удастся сопоставить какой-либо *.jar as:

 -bash: no match: *.jar
  

Комментарии:

1. shopt : Установить и отменить параметры оболочки.

2. Я бы подумал failglob , что здесь может быть более уместно, поскольку запуск java -jar , вероятно, не является желательным результатом неудачного расширения. Хотя в любом случае, я думаю, вы просто получаете какое-то сообщение об ошибке…

3. @twalberg: Согласен. Хотя вы получаете сообщения об ошибках в обоих случаях (по крайней мере, в этом случае), только тот, который вы получите, failglob укажет на непосредственную проблему ( bash: no match: *.jar ) . При nullglob этом команда не может завершиться ошибкой в других случаях и привести к нежелательному поведению.

Ответ №2:

Объяснение и справочная информация

Проблема OP заключалась НЕ в сглаживании как таковом — для работы глобуса (шаблона) специальные символы шаблона, такие как * , должны быть без кавычек, что работает даже в строках, которые частично заключены в одинарные или двойные кавычки, как правильно сделал OP в своем вопросе:

 "$dir/"*.jar # OK, because `*` is unquoted
  

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

  • В рассматриваемом случае "$dir" произошло расширение до каталога, который не содержал *.jar файлов, и, таким образом, результирующая строка, переданная в java , заканчивалась буквальным *.jar ( '<value of $dir>/*.jar' ) , что из-за отсутствия ссылки на фактические .jar файлы привело к ошибке, указанной в вопросе.

Параметры оболочки управляют глобализацией (более формально называемой расширением имени пути):

  • set -f ( shopt -so noglob ) полностью отключает глобализацию, так что строки (символы) без кавычек, которые обычно приводят к обработке строки как глобуса, обрабатываются как литералы.
  • shopt -s nullglob изменяет поведение по умолчанию на расширение несоответствующего глобуса до пустой строки.
  • shopt -s failglob изменяет поведение по умолчанию на сообщение об ошибке и установку кода выхода равным 1 в случае несоответствующего глобуса, даже без выполнения команды под рукой — см. Ниже о подводных камнях.
  • Существуют и другие параметры, связанные с глобализацией, не относящиеся к этому обсуждению — чтобы просмотреть их список, запустите { shopt -o; shopt; } | fgrep glob . Для описания выполните поиск по их именам в man bash .

Надежные решения для объединения

Примечание: установка параметров оболочки глобально влияет на текущую оболочку, что проблематично, поскольку сторонний код обычно делает — разумное — предположение, что действуют значения по умолчанию. Таким образом, хорошей практикой является только временное изменение параметров оболочки (изменение, выполнение действия, восстановление) или локализация изменения их эффекта с помощью подоболочки ( (...) ).


shopt -s nullglob

  • Полезно для перечисления совпадений в цикле с for помощью — это гарантирует, что цикл никогда не вводится, если совпадений нет:
 shopt -s nullglob # expand non-matching globs to empty string
for f in "$dir/"*.jar; do
  # If the glob matched nothing, we never get here.
  # !! Without `nullglob`, the loop would be entered _once_, with 
  # !! '<value of $dir>/*.jar'.
done
  
  • Проблематично при использовании с аргументами команд, которые имеют поведение по умолчанию при отсутствии аргументов имени файла, поскольку это может привести к неожиданному поведению:
 shopt -s nullglob # expand non-matching globs to empty string
wc -c "$dir/"*.jar # !! If no matches, expands to just `wc -c`
  

Если глобус ничему не соответствует, просто выполняется wc -c , что не приводит к сбою, а вместо этого начинает считывание stdin входных данных (при интерактивном запуске это будет просто ждать интерактивных строк ввода, пока не завершится с Ctrl-Dпомощью ).


shopt -s failglob

  • Полезно для сообщения определенного сообщения об ошибке, особенно в сочетании с set -e для автоматического прерывания сценария в случае, если глобус ничего не соответствует:
 set -e  # abort automatically in case of error
shopt -s failglob # report error if a glob matches nothing
java -jar "$dir/"*.jar  # script aborts, if this glob doesn't match anything
  
  • Проблематично, когда требуется знать конкретную причину ошибки и в сочетании с || <command in case of failure> идиомой:
 shopt -s failglob # report error if a glob matches nothing
# !! DOES NOT WORK AS EXPECTED.
java -jar "$dir/"*.jar || { echo 'No *.jar files found.' >amp;2; exit 1; }
# !! We ALWAYS get here (but exit code will be 1, if glob didn't match anything).
  

Поскольку при failglob использовании on bash никогда даже не выполняется команда под рукой, если глобализация завершается неудачно, || предложение также не выполняется, и общее выполнение продолжается.

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


Альтернативное решение без изменения параметров оболочки:

Приложив немного больше усилий, вы можете выполнить собственную проверку на наличие несоответствующих глобусов:

Ad-hoc:

 glob="$dir/*.jar"
[[ -n $(shopt -s nullglob; echo $glob) ]] || 
  { echo 'No *.jar files found.' >amp;2; exit 1; }
java -jar $glob
  

$(shopt -s nullglob; echo $glob) устанавливает nullglob , а затем расширяет глобус с echo помощью , так что подоболочка либо возвращает совпадающие имена файлов, либо, если ничего не совпадает, пустую строку; этот вывод, благодаря команде substitution ( $(...) ) , передается -n , который проверяет, является ли строка пустой, так что общий [[ ... ]] код выхода условия отражает, соответствует ли что-то(код выхода 0 ) или нет (код выхода 1 ).

Обратите внимание, что любая команда внутри подстановки команд выполняется в подоболочке, что гарантирует, что эффект shopt -s nullglob применяется только к этой самой подоболочке и, следовательно, не изменяет глобальное состояние.

Также обратите внимание, что вся правая часть присваивания переменной glob="$dir/*.jar" заключена в двойные кавычки, чтобы проиллюстрировать тот факт, что кавычки в отношении глобализации имеют значение, когда на переменную ссылаются позже, а не когда она определена. Ссылки без кавычек $glob , которые будут использоваться позже, гарантируют, что вся строка интерпретируется как глобус.

С небольшой вспомогательной функцией:

 # Define simple helper function.
exists() { [[ -e $1 ]]; }

glob="$dir/*.jar"
exists $glob || { echo 'No *.jar files found.' >amp;2; exit 1; }
java -jar $glob
  

Вспомогательная функция использует преимущества оболочки, применяющей глобализацию при вызове функции, и передает результаты глобализации (расширение имени пути) в качестве аргументов. Затем функция просто проверяет, ссылается ли 1-й результирующий аргумент (если таковой имеется) на существующий элемент, и соответствующим образом устанавливает код выхода (это будет работать независимо от того nullglob , действует он или нет).