В чем разница между $myvar и "$myvar" в переменных Bash? (Особое странное поведение)

В чем разница между $myvar и "$myvar" в переменных Bash? (Особое странное поведение)

Общий вопрос:

Я знаю, что в 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.

Связанный контент