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

21

Скажем, у меня есть сценарий:

some-command "$var1" "$var2" ...

И, в случае, если var1это пусто, я бы предпочел, чтобы он был заменен ничем вместо пустой строки, чтобы выполняемая команда была:

some-command "$var2" ...

и нет:

some-command '' "$var2" ...

Есть ли более простой способ, чем тестирование переменной и ее условное включение?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

Есть ли подстановка параметров, которая может расширяться до нуля в bash, zsh и т. П.? Я мог бы все еще хотеть использовать globbing в остальных аргументах, поэтому отключение этого и снятие кавычек с переменной не вариант.

Мур
источник
Я знал, что видел и, вероятно, использовал это раньше, но это оказалось трудным для поиска. Теперь, когда Майкл показал синтаксис, я вспомнил, где впервые увидел его достаточно быстро: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
Muru
Если вы знаете, что это какая-то подстановка параметров, почему вы не заглянули в раздел расширения параметров на manстранице? (-;
Филиппос
1
@Philippos Я не знал, что это было в то время, только то, что видел или использовал раньше. Известные известные и неизвестные известные. :(
Муру,
1
дополнительные cookie-файлы за упоминание использования массива для хранения аргументов в самом вопросе.
ilkkachu

Ответы:

29

У Posix-совместимых оболочек и Bash есть ${parameter:+word} :

Если параметр не установлен или ноль, ноль должен быть заменен; в противном случае расширение слова (или пустая строка, если слово опущено) должно быть заменено.

Так что вы можете просто сделать:

${var1:+"$var1"}

и должны var1быть проверены и "$var1"использоваться, если оно установлено и не пусто (с обычными правилами двойных кавычек). В противном случае это расширяется в ничто. Обратите внимание, что здесь указана только внутренняя часть, а не вся.

То же самое работает и в Zsh. Вы должны повторить переменную, чтобы она не была идеальной, но она работает именно так, как вы хотели.

Если вы хотите, чтобы переменная set-but-empty расширялась до пустого аргумента, используйте ${var1+"$var1"}вместо этого.

Майкл Гомер
источник
1
Достаточно хорошо для меня. Таким образом, в этом все не указано, только wordчасть.
Муру
1
Поскольку вопрос задавался для «bash, zsh или тому подобного» (и для архива вопросов и ответов), я отредактировал, чтобы отразить, что это функция posix. Даже если вы добавите тег / bash к вопросу после моего комментария. (-;
Филиппос
2
Я взял на себя смелость редактирования в разнице между :+и +.
ilkkachu
Большое спасибо за POSIX-совместимое решение! Я стараюсь использовать sh/ dashдля потенциальных сценариев chokepoint, поэтому всегда ценю, когда кто-то показывает, как это возможно, не прибегая к Bash.
JamesTheAwesomeDude
Я искал противоположный эффект (разверните на что-нибудь еще, если он начинается с нуля). Оказывается, эту логику можно инвертировать, изменив + на a - как в: $ {empty_var: -replacement}
Алекс Янсен
5

Вот что zshделает по умолчанию, когда вы опускаете кавычки:

some-command $var1 $var2

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

Вы можете сделать то же самое с другими POSIX-подобными оболочками, если отключите split и glob:

(IFS=; set -o noglob; some-command $var1 $var2)

Теперь я бы сказал, что если ваша переменная может иметь значение 0 или 1, это должен быть массив, а не скалярная переменная, и используйте:

some-command "${var1[@]}" "${var2[@]}"

А использование var1=(value)при var1должен содержать одно значение, var1=('')когда она содержит один пустое значение, и var1=()когда она не содержит не значение.

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

Я столкнулся с этим, используя rsync в bash-скрипте, который запускал команду с или без -nпереключения для пробного запуска . Оказывается, что rsync и ряд команд gnu принимают ''в качестве действительного первого аргумента и действуют иначе, чем если бы их не было.

Отладка заняла довольно много времени, поскольку нулевые параметры почти полностью невидимы.

Кто-то из списка rsync показал мне способ избежать этой проблемы, а также значительно упростил мое кодирование. Если я правильно понимаю, это вариант последнего предложения @ Stéphane Chazelas.

Создайте аргументы вашей команды в нескольких отдельных переменных. Они могут быть установлены в любом порядке или логике, которые подходят для проблемы.

Затем, в конце, используйте переменные для создания массива со всем на своем месте и используйте его в качестве аргументов для фактической команды.

Таким образом, команда выполняется только в одном месте кода, а не повторяется для каждого варианта аргументов.

Любая пустая переменная просто исчезает при использовании этого метода.

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

Пример:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...
Джо
источник
Это худший способ сделать то, что я описал в вопросе (условно построить массив аргументов). Оставляя переменные без кавычек, кто знает, какие проблемы вы оставляете открытыми для себя.
Муру
@muru Ты прав, но мне все еще нужно что-то подобное. Я не совсем понимаю, как это исправить, используя приемы из других ответов. Было бы замечательно увидеть фрагмент кода, сделанный правильно, чтобы собрать все вместе. Я собираюсь поиграть с последним вариантом Стефана позже, так как это может сделать то, что я хочу.
Джо
Как paste.ubuntu.com/26383847 ?
Муру
Просто вернулся к этому. Спасибо за пример. Это имеет смысл. Я собираюсь работать с этим.
Джо
У меня есть похожий вариант использования здесь, но вы можете сделать что-то вроде этого: if ["$ dry" == "true"]; тогда сухой = "- n"; fi ... поэтому, если переменная dry имеет значение true, вы задаете для нее значение -n, ​​а затем создаете свою команду rsync как обычно и получаете ее следующим образом: rsync $ {dry1: + "$ dry1"} ... если она равна нулю ничего не произойдет, иначе это станет -n в вашей команде
Freedo