Общий вопрос:
Я знаю, что в Bash использовать переменную myvar
можно двумя способами:
# Define a variable:
bash$ myvar="two words"
# Method one to dereference:
bash$ echo $myvar
two words
# Method two to dereference:
bash$ echo "$myvar"
two words
В приведенном выше случае поведение идентично. Это из-за того, как echo
работает. В других утилитах Unix, группируются ли слова вместе с двойными кавычками, будет иметь огромное значение:
bash$ myfile="Cool Song.mp3"
bash$ rm "$myfile" # Deletes "Cool Song.mp3".
bash$ rm $myfile # Tries to delete "Cool" and "Song.mp3".
Мне интересно, в чем глубинное значение этой разницы. Самое главное, как я могу увидеть, что именно будет передано команде, чтобы я мог увидеть, правильно ли она процитирована?
Конкретный странный пример:
Я просто напишу код с наблюдаемым поведением:
bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate" # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.
Зачем мне двойные кавычки? Что именно видит git-log после разыменования переменной без двойных кавычек?
Но теперь посмотрите на это:
bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace # Now THIS works great.
bash$ git log "$nospace" # This kind of works, here is a snippet:
# From git-log output:
Date: "2018-04-12"
Фу, почему в распечатанном выводе теперь двойные кавычки? Похоже, если двойные кавычки не нужны, они не удаляются, а интерпретируются как буквальные символы кавычек, если и только если они не нужны.
Что передается Git в качестве аргументов?Хотел бы я знать, как это выяснить.
Чтобы усложнить ситуацию, я написал скрипт на Python, argparse
который просто выводит все аргументы (как их интерпретирует Bash, то есть с литералами в двойных кавычках, где Bash считает, что они являются частью аргумента, и со словами, сгруппированными или не сгруппированными по усмотрению Bash), и argparse
скрипт на Python ведет себя очень рационально. К сожалению, я думаю, что argparse
он может молча исправлять известную проблему с Bash и таким образом скрывать испорченные вещи, которые Bash ему передает. Это всего лишь предположение, я понятия не имею. Может быть, git-log тайно портит то, что Bash ему передает.
Или, может быть, я просто вообще не понимаю, что происходит.
Спасибо.
Отредактировано Редактировать:Позвольте мне сказать это сейчас, прежде чем будут какие-либо ответы: я знаю, что я могуможет бытьиспользуйте одинарные кавычки вокруг всего этого и не экранируйте двойные кавычки. Это на самом деле работает немного лучше для моей первоначальной проблемы с использованием git-log, но я тестировал это в некоторых других контекстах, и это примерно так же непредсказуемо и ненадежно. Что-то странное происходит с кавычками внутри переменных. Я даже не буду публиковать все странности, которые произошли с одинарными кавычками.
Редактировать 2 - Это тоже не работает:У меня только что возникла замечательная идея, но она совершенно не работает:
bash$ mydate="--date=format:%Y-%m-%d\ T%H"
bash$ git log "$mydate"
# Git log output has this:
Date: 2018-04-12\ T23
Поэтому кавычки здесь не нужны,нов строке даты есть буквальный символ обратной косой черты. Также, git log $mydate
без кавычек выводятся ошибки, с обратной косой чертой-пробелом в переменной.
решение1
Другой подход:
При запуске git log --format="foo bar"
эти кавычки не интерпретируются git – они удаляются оболочкой (и защищают цитируемый текст от разделения). Это приводит к одному аргументу:
--format=foo bar
Однако, когда не цитируетсяпеременныерасширяются, результаты проходят через разбиение слов, нонетчерез снятие кавычек. Так что если ваша переменная содержит --format="foo bar"
, она раскрывается в эти аргументы:
--format="foo
bar"
Это можно проверить с помощью:
printf '%s\n' $переменная
...а также любой простой скрипт, который выводит полученные аргументы.
#!/usr/bin/env perl для $i (0..$#ARGV) { распечатать ($i+1)." = ".$ARGV[$i]."\n"; }
#!/usr/bin/env питон3 импортировать систему для i, arg в enumerate(sys.argv): print(i, "=", арг)
Если у вас всегда есть доступ к bash, предпочтительным решением будет использованиемножествопеременные:
myvar=( --format="foo bar" )
При этом обычный разбор выполняется во время назначения, а не во время расширения. Вы используете этот синтаксис для расширения содержимого переменной, каждый элемент получает свой собственный arg:
git log "${myvar[@]}"
решение2
Почему ваша исходная команда не работает?
bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate" # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.
Ты спрашиваешь:
Зачем мне двойные кавычки? Что именно видит git-log после разыменования переменной без двойных кавычек?
Если не использовать двойные кавычки $mydate
, переменная будет раскрыта дословно, а строка оболочки перед выполнением будет выглядеть следующим образом:
git log --date=format:"%Y-%m-%d T%H"
^————————————^—————— literal quotes
Здесь вы (без необходимости) добавили буквальные кавычки, используя \"
при назначении переменной.
Поскольку команда будет подвергатьсяразделение слов, git
получит три аргумента, log
, --date-format:"%Y-%m%-d
и T%H"
, поэтому будет выдавать сообщение о том, что не найдено ни одного коммита или объекта с именем T%H"
.
Какой подход правильный?
Если вы хотите сохранить аргументы вместе, и если аргумент содержит пробелы, вам придется заключить его в кавычки.Как правило, всегда заключайте переменные в двойные кавычки.
Это работает, даже если внутри переменной есть пробел:
mydate="--date=format:%Y-%m-%d T%H"
git log "$mydate"
Теперь третий аргумент для git
будет $mydate
, включая пробел, который вы изначально указали. Все кавычки удаляются оболочкой перед передачей в git
.
Вам просто не нужны дополнительные кавычки — если вы хотите git
увидеть только один аргумент, заключите этот аргумент в кавычки при передаче переменной "$mydate"
.
Вы также спрашиваете:
bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace # Now THIS works great.
bash$ git log "$nospace" # This kind of works, here is a snippet:
# From git-log output:
Date: "2018-04-12"
Ваш вопрос:
Фу, почему теперь в распечатанном документе присутствуют двойные кавычки?
Потому что вы снова включилибуквальныйкавычки в аргументе (путем их экранирования), которые превращаются, скажем, в «настоящие» кавычки, когда вы забываете заключить переменную в кавычки в вашей фактической команде. Я говорю «забываете», потому что использование переменных без кавычек в командах оболочки обычно просто навлекает на вас неприятности — а здесь это отмена ошибки, которую вы сделали, изначально указывая переменную.
PS: Я знаю, что это все сбивает с толку, но это Bash, и он следует некоторым четким правилам. Здесь нет ошибки. Aсвязанное эссеоб именах файлов в оболочке также весьма показателен, поскольку затрагивает проблему обработки пробелов в Bash.