Как избежать одинарных кавычек внутри одинарных строк


1019

Допустим, у вас есть Bash, aliasкак:

alias rxvt='urxvt'

который работает отлично.

Однако:

alias rxvt='urxvt -fg '#111111' -bg '#111111''

не будет работать, и не будет:

alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''

Итак, как вы в конечном итоге сопоставляете открывающие и закрывающие кавычки внутри строки после того, как вы избежали кавычек?

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''

кажется неловким, хотя он будет представлять одну и ту же строку, если вам разрешено объединять их таким образом.


16
Вы понимаете, что вам не нужно использовать одинарные кавычки для псевдонима? Двойные кавычки намного проще.
Текнопаул


3
Вложенные двойные кавычки являются экранируемыми, "\""поэтому их следует использовать по возможности перед ответом @ liori.
алан

7
Двойные кавычки ведут себя совершенно иначе, чем одиночные кавычки в * nix (включая Bash и связанные с ними инструменты, такие как Perl), поэтому замена двойных кавычек, когда есть проблема с одинарными кавычками, НЕ является хорошим решением. Двойные кавычки указывают, что переменные $ ... должны быть подставлены перед выполнением, а одинарные кавычки указывают, что $ ... должны трактоваться буквально.
Чак

Если вы думаете, я использовал двойные кавычки, но это все еще не работает , снова введите ваш сценарий.
Сэми Бенчериф

Ответы:


1456

Если вы действительно хотите использовать одинарные кавычки во внешнем слое, помните, что вы можете склеить оба вида цитат. Пример:

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
 #                     ^^^^^       ^^^^^     ^^^^^       ^^^^
 #                     12345       12345     12345       1234

Объяснение того, как '"'"'интерпретируется просто ':

  1. ' Завершите первую цитату, которая использует одинарные кавычки.
  2. " Начните вторую цитату, используя двойные кавычки.
  3. ' Процитированный персонаж.
  4. " Завершите вторую цитату, используя двойные кавычки.
  5. ' Начните третью цитату, используя одинарные кавычки.

Если вы не поместите пробелы между (1) и (2) или между (4) и (5), оболочка будет интерпретировать эту строку как одно длинное слово.


5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Это работает, когда в строке псевдонима есть как одинарные, так и двойные кавычки!
Uphill_ What '1

17
Моя интерпретация: bash неявно объединяет строковые выражения в разных кавычках.
Бенджамин Аткин

2
работал для меня, пример двойных кавычек:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
JAMESSTONEco

2
Конечно, не самое читаемое решение. Он чрезмерно использует одинарные кавычки там, где они на самом деле не нужны.
oberlies

26
Я утверждаю, что '\''это гораздо более читабельно в большинстве контекстов, чем '"'"'. На самом деле, первое почти всегда четко различается внутри строки в одинарных кавычках, и, таким образом, это просто вопрос его семантического отображения в значении «это экранированная кавычка», как \"в случае строк в двойных кавычках. Принимая во внимание, что последний сливается в одну строку цитат и во многих случаях нуждается в тщательном осмотре, чтобы правильно различить.
mtraceur

263

Я всегда просто заменяю каждую встроенную одинарную кавычку на последовательность: '\''(то есть: кавычка с кавычками в обратном слэше), которая закрывает строку, добавляет экранированную одинарную кавычку и снова открывает строку.


Я часто использую функцию «quotify» в моих скриптах Perl, чтобы сделать это для меня. Шаги будут:

s/'/'\\''/g    # Handle each embedded quote
$_ = qq['$_']; # Surround result with single quotes.

Это в значительной степени заботится обо всех случаях.

Жизнь становится веселее, когда вы внедряете evalв свои shell-скрипты. По сути, вы должны заново пересчитать все!

Например, создайте сценарий Perl с именем quotify, содержащий приведенные выше операторы:

#!/usr/bin/perl -pl
s/'/'\\''/g;
$_ = qq['$_'];

затем используйте его для генерации строки в правильных кавычках:

$ quotify
urxvt -fg '#111111' -bg '#111111'

результат:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

который затем можно скопировать / вставить в команду псевдонима:

alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

(Если вам нужно вставить команду в eval, запустите цитату еще раз:

 $ quotify
 alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

результат:

'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

который можно скопировать / вставить в eval:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

1
Но это не перл. И, как Стив B указывал выше, со ссылкой на «справочное руководство gnu», вы не можете избежать кавычек в bash внутри одного и того же типа кавычек. И на самом деле, не нужно экранировать их в альтернативных кавычках, например, «» является допустимой строкой в ​​одинарных кавычках, а «» является допустимой строкой в ​​двойных
кавычках

8
@nicerobot: я добавил пример, показывающий, что: 1) я не пытаюсь экранировать кавычки внутри одного и того же типа кавычек, 2) ни в альтернативных кавычках, и 3) Perl используется для автоматизации процесса генерации действительного Строка bash, связывающая вложенные цитаты
Адриан Пронк

18
Первый абзац сам по себе является ответом, который я искал.
Дейв Кози

9
Это то , что делает баш, а, типа set -xи , echo "here's a string"и вы увидите , что Баш Выполняет echo 'here'\''s a string'. ( set +xчтобы вернуть нормальное поведение)
arekolek

196

Поскольку синтаксис Bash 2.04$'string' (вместо просто 'string'; предупреждение: не путайте с $('string')) является еще одним механизмом цитирования, который допускает escape-последовательности, подобные ANSI C, и выполняет расширение до версии в одинарных кавычках.

Простой пример:

  $> echo $'aa\'bb'
  aa'bb

  $> alias myvar=$'aa\'bb'
  $> alias myvar
  alias myvar='aa'\''bb'

В твоем случае:

$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
$> alias rxvt
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Обычные экранирующие последовательности работают как положено:

\'     single quote
\"     double quote
\\     backslash
\n     new line
\t     horizontal tab
\r     carriage return

Ниже приведена копия + вставлена ​​соответствующая документация из man bash (версия 4.4):

Слова вида $ 'string' обрабатываются специально. Слово расширяется до строки, символы с обратной косой чертой заменяются в соответствии со стандартом ANSI C. Escape-последовательности с обратной косой чертой, если они есть, декодируются следующим образом:

    \a     alert (bell)
    \b     backspace
    \e
    \E     an escape character
    \f     form feed
    \n     new line
    \r     carriage return
    \t     horizontal tab
    \v     vertical tab
    \\     backslash
    \'     single quote
    \"     double quote
    \?     question mark
    \nnn   the eight-bit character whose value is the octal 
           value nnn (one to three digits)
    \xHH   the eight-bit character whose value is the hexadecimal
           value HH (one or two hex digits)
    \uHHHH the Unicode (ISO/IEC 10646) character whose value is 
           the hexadecimal value HHHH (one to four hex digits)
    \UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value 
               is the hexadecimal value HHHHHHHH (one to eight 
               hex digits)
    \cx    a control-x character

Расширенный результат заключен в одинарные кавычки, как если бы знак доллара отсутствовал.


См. Цитаты и экранирование: ANSI C как строки на вики bash-hackers.org для более подробной информации. Также обратите внимание, что файл «Bash Changes» ( обзор здесь ) много упоминает об изменениях и исправлениях ошибок, связанных с $'string'механизмом цитирования.

Согласно unix.stackexchange.com Как использовать специальный символ как обычный? он должен работать (с некоторыми вариациями) в bash, zsh, mksh, ksh93 и FreeBSD и busybox sh.


может использоваться, но строка с одинарными кавычками здесь не является реальной строкой с одинарными кавычками, содержимое этой строки может быть echo $'foo\'b!ar'!ar': event not found
перехвачено

2
На моей машине > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41

1
Да, это и есть причина "май", у меня это было на Red Hat 6.4, конечно, на старой версии bash.
Regilero

Bash ChangeLog содержит множество исправлений, связанных с, $'так что, вероятно, самый простой способ - попробовать это самостоятельно на старых системах.
mj41

знать: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions.взято из tiswww.case.edu/php/chet/bash/CHANGES . Все еще работает в 4.3.42, но не в 4.3.48.
stiller_leser

49

Я не вижу записи в его блоге (ссылка PLS?), Но согласно справочному руководству GNU :

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

так что bash не поймет

alias x='y \'z '

однако вы можете сделать это, если заключите в двойные кавычки:

alias x="echo \'y "
> x
> 'y


Содержимое, заключенное в двойные кавычки, оценивается, поэтому правильное решение кажется заключить в одинарные кавычки в двойных кавычках, как предполагает Лиори.
Петр Доброгост

3
Это фактический ответ на вопрос. Хотя принятый ответ может дать решение, технически он отвечает на вопрос, который не был задан.
Мэтью Дж

3
Мэтью, вопрос был о том, чтобы избежать одинарных кавычек внутри одинарных кавычек. Этот ответ просит пользователя изменить свое поведение, и если у вас есть препятствия для использования двойных кавычек (как следует из названия вопроса), этот ответ не поможет. Это довольно полезно (хотя и очевидно), и как таковое заслуживает одобрения, но принятый ответ решает точную проблему, о которой спрашивал Оп.
Фернандо Кордейру,

Не нужно заключать одинарную кавычку в строку двойной кавычки.
Мэтью Д. Шолфилд,

32

Я могу подтвердить, что с помощью '\'' одинарной кавычки внутри строки в одинарных кавычках работает в Bash, и это можно объяснить так же, как аргумент «склеивания» из предыдущего потока. Предположим, у нас есть строка в кавычках: 'A '\''B'\'' C'(здесь все кавычки одинарные). Если он передается эхо, он печатает следующее: A 'B' C. В каждой '\''первой кавычке закрывается текущая строка в одинарных кавычках, следующая \'склеивает одинарную кавычку с предыдущей строкой ( \'это способ указать одиночную кавычку без начала строки в кавычках), а последняя кавычка открывает еще одну строку в одинарных кавычках.


2
Это вводит в заблуждение, этот синтаксис «\» не идет «внутри» строки в одинарных кавычках. В этом утверждении 'A' \ '' B '\' 'C' вы объединяете строки 5 \ escape и одинарные кавычки
teknopaul

1
@teknopaul В alias something='A '\''B'\'' C'результате присваивания получается somethingодна строка, поэтому даже если правая часть присваивания не является технически единственной строкой, я не думаю, что это имеет большое значение.
Теему Лейсти

Хотя это работает в вашем примере, это не технически обеспечивает решение для того, как вставить апостроф внутри одной строки в кавычках. Вы уже объяснили это, но да, это так 'A ' + ' + 'B' + ' + ' C'. Другими словами, решение для вставки символов одинарных кавычек внутри строки в одинарных кавычках должно позволить мне создать такую ​​строку и распечатать ее. Однако это решение не будет работать в этом случае. STR='\''; echo $STR, Как и задумано, BASH действительно не позволяет этого.
krb686

@mikhail_b, да, '\''работает на bash. Не могли бы вы указать, какие разделы gnu.org/software/bash/manual/bashref.html определяют такое поведение?
Цзинго Яо

20

Обе версии работают либо с конкатенацией с использованием экранированного символа одинарных кавычек (\ '), либо с конкатенацией, заключающей символ одинарных кавычек в двойные кавычки ("'").

Автор вопроса не заметил, что в конце его последней попытки побега была добавлена ​​одиночная кавычка ('):

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
           │         │┊┊|       │┊┊│     │┊┊│       │┊┊│
           └─STRING──┘┊┊└─STRIN─┘┊┊└─STR─┘┊┊└─STRIN─┘┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      └┴─────────┴┴───┰───┴┴─────────┴┘│
                          All escaped single quotes    │
                                                       │
                                                       ?

Как вы можете видеть в предыдущем хорошем фрагменте ASCII / Unicode, за последней экранированной одинарной кавычкой (\ ') следует ненужная одинарная кавычка ('). Использование подсветки синтаксиса, подобной тому, что представлен в Notepad ++, может оказаться очень полезным.

То же самое верно для другого примера, подобного следующему:

alias rc='sed '"'"':a;N;$!ba;s/\n/, /g'"'"
alias rc='sed '\'':a;N;$!ba;s/\n/, /g'\'

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

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214

3
Переделан для иллюстрации ASCII Table :)
php-dev

16

Простой пример экранирования кавычек в оболочке:

$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc

Это сделано, заканчивая уже открытый one ( '), помещая экранированный one ( \'), затем открывая другой ( '). Этот синтаксис работает для всех команд. Это очень похоже на первый ответ.


15

Я не обращаю особого внимания на проблему цитирования, потому что иногда бывает разумно рассмотреть альтернативный подход.

rxvt() { urxvt -fg "#${1:-000000}" -bg "#${2:-FFFFFF}"; }

который вы можете затем назвать как:

rxvt 123456 654321

идея в том, что теперь вы можете использовать псевдоним, не заботясь о кавычках:

alias rxvt='rxvt 123456 654321'

или, если вам #по какой-либо причине необходимо включить во все вызовы:

rxvt() { urxvt -fg "${1:-#000000}" -bg "${2:-#FFFFFF}"; }

который вы можете затем назвать как:

rxvt '#123456' '#654321'

тогда, конечно, псевдоним:

alias rxvt="rxvt '#123456' '#654321'"

(Ой, я думаю, что я как-то обратился к цитированию :)


1
Я пытался поместить что-то в одинарные кавычки, которые были в двойных кавычках, которые, в свою очередь, были в одинарных кавычках. Хлоп. Спасибо за ваш ответ "попробуйте другой подход". Это имело значение.
Клинтон Блэкмор

1
Я опоздал на 5 лет, но вы не пропустили ни одной кавычки в своем последнем псевдониме?
Жюльен

1
@Julien Я не вижу проблемы ;-)
nicerobot

11

Поскольку нельзя заключать в одинарные кавычки одинарные кавычки, самый простой и читаемый вариант - использовать строку HEREDOC

command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)

alias rxvt=$command

В приведенном выше коде HEREDOC отправляется на cat команде, а ее выход назначается переменной через нотацию подстановки команд$(..)

Необходимо поместить одинарную кавычку вокруг HEREDOC, поскольку она находится в пределах $()


Я хотел бы прокрутить это далеко раньше - я заново изобрел этот подход и пришел сюда, чтобы опубликовать его! Это намного чище и удобочитаемее, чем все другие способы выхода из положения. Кроме того, он не будет работать с некоторыми оболочками, отличными от bash, такими как dashоболочка по умолчанию в сценариях запуска Ubuntu и в других местах.
Корни

Спасибо! то, что я искал, способ определить команду как есть через heredoc и передать команду auto escaped в ssh. Кстати, cat << COMMAND без кавычек позволяет интерполировать vatiables внутри команды и работает также для этого подхода.
Игорь Твердовский

10

Я просто использую шелл-коды .. например \x27или \\x22как применимо. Никаких хлопот, правда.


Не могли бы вы показать пример этого в действии? Для меня это просто печатает буквальный x27(на Centos 6.6)
Уилл Шеппард

6

Большинство из этих ответов затрагивают конкретный случай, о котором вы спрашиваете. Существует общий подход друг , и я разработал , что позволяет произвольно процитировать в случае , если вам нужно процитировать Баш команд через несколько слоев расширения оболочки, например, через SSH, su -c, bash -cи т.д. Существует один основной примитив вам нужно, здесь в родном bash:

quote_args() {
    local sq="'"
    local dq='"'
    local space=""
    local arg
    for arg; do
        echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
        space=" "
    done
}

Это делает именно то, что говорит: он заключает в кавычки каждый аргумент индивидуально (конечно, после расширения bash):

$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'

Это делает очевидную вещь для одного уровня расширения:

$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2

(Обратите внимание, что двойные кавычки $(quote_args ...)необходимы для того, чтобы превратить результат в один аргумент bash -c.) И его можно использовать более широко для правильного цитирования через несколько уровней расширения:

$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2

Приведенный выше пример:

  1. shell-кавычки каждый аргумент к внутреннему quote_argsиндивидуально и затем объединяет полученный результат в единый аргумент с внутренними двойными кавычками.
  2. кавычки bash,-c и уже один раз процитированный результат с шага 1, а затем объединяет результат в один аргумент с внешними двойными кавычками.
  3. отправляет этот беспорядок в качестве аргумента к внешнему bash -c.

Это идея в двух словах. Вы можете сделать довольно сложные вещи с этим, но вы должны быть осторожны с порядком оценки и с тем, какие подстроки цитируются. Например, следующее делает неправильные вещи (для некоторого определения «неправильно»):

$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure

В первом примере, Баш немедленно расширяется quote_args cd /; pwd 1>&2на две отдельные команды, quote_args cd /и pwd 1>&2, таким образом , по - прежнему УХО , /tmpкогда pwdвыполняется команда. Второй пример иллюстрирует аналогичную проблему для сглаживания. Действительно, та же самая основная проблема возникает со всеми расширениями bash. Проблема здесь в том, что подстановка команд не является вызовом функции: она буквально оценивает один скрипт bash и использует его вывод как часть другого скрипта bash.

Если вы попытаетесь просто избежать операторов оболочки, вы потерпите неудачу, потому что полученная в результате строка bash -cявляется просто последовательностью строк в индивидуальном кавычке, которые затем не интерпретируются как операторы, что легко увидеть, если вы откроете строку, которая будет были переданы в bash:

$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'

Проблема в том, что вы преувеличиваете. Вам нужно, чтобы операторы были заключены в кавычки как входные данные для включения bash -c, что означает, что они должны находиться за пределами $(quote_args ...)подстановки команд.

Следовательно, что вам нужно сделать в самом общем смысле, это заключить в кавычки каждое слово команды, не предназначенной для расширения во время подстановки команд, отдельно и не применять никаких дополнительных кавычек к операторам оболочки:

$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success

Как только вы это сделаете, вся строка станет честной игрой для дальнейшего цитирования на произвольных уровнях оценки:

$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success

и т.п.

Эти примеры могут показаться перегруженными, если учесть, что такие слова, как success, sbinи pwdне нужно заключать в кавычки, но ключевой момент, который следует помнить при написании сценария с произвольным вводом, заключается в том, что вы хотите процитировать все, в чем вы не уверены, что не ' не нужно цитировать, потому что вы никогда не знаете, когда пользователь добавит Robert'; rm -rf /.

Чтобы лучше понять, что происходит под одеялом, вы можете поиграть с двумя небольшими вспомогательными функциями:

debug_args() {
    for (( I=1; $I <= $#; I++ )); do
        echo -n "$I:<${!I}> " 1>&2
    done
    echo 1>&2
}

debug_args_and_run() {
    debug_args "$@"
    "$@"
}

это перечислит каждый аргумент команды перед ее выполнением:

$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

Привет Кайл. Ваше решение работало отлично подходит для случая у меня был, когда мне нужно было передать группу аргументов в качестве единственного аргумента: vagrant ssh -c {single-arg} guest. В {single-arg}потребности следует рассматривать в качестве одного арга , потому что бродяга принимает следующий ARG после него имени гостя. Порядок не может быть изменен. Но мне нужно было передать команду и ее аргументы внутри {single-arg}. Таким образом , я использовал ваш quote_args()процитировать команду и ее аргументы, и поставить двойные кавычки вокруг результата, и он работал как шарм: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Спасибо!!!
Андреас Майер

6

ИМХО, реальный ответ заключается в том, что вы не можете избежать одинарных кавычек в одиночных кавычках.

Это невозможно.

Если мы предполагаем, что мы используем Bash.

Из руководства Bash ...

Enclosing characters in single quotes preserves the literal value of each
character within the quotes.  A single quote may not occur
between single quotes, even when preceded by a backslash.

Вам нужно использовать один из других механизмов выхода из строки "или \

В этом нет ничего волшебного alias что требует использования одинарных кавычек.

Оба следующие работают в bash.

alias rxvt="urxvt -fg '#111111' -bg '#111111'"
alias rxvt=urxvt\ -fg\ \'#111111\'\ -bg\ \'#111111\'

Последний использует \ для экранирования символа пробела.

В # 111111 нет ничего волшебного, что требует одинарных кавычек.

Следующие опции достигают того же результата, что и другие две опции, в том смысле, что псевдоним rxvt работает как положено.

alias rxvt='urxvt -fg "#111111" -bg "#111111"'
alias rxvt="urxvt -fg \"#111111\" -bg \"#111111\""

Вы также можете избежать неприятностей # напрямую

alias rxvt="urxvt -fg \#111111 -bg \#111111"

«Реальный ответ заключается в том, что вы не можете избежать одинарных кавычек в одинарных кавычках». Это технически верно. Но у вас может быть решение, которое начинается с одинарной кавычки, заканчивается одинарной кавычкой и содержит только одинарные кавычки в середине. stackoverflow.com/a/49063038
wisbucky

Не спасаясь, только конкатенацией.
Текнопауль

4

В данном примере просто использовали двойные кавычки вместо одинарных кавычек в качестве внешнего escape-механизма:

alias rxvt="urxvt -fg '#111111' -bg '#111111'"

Этот подход подходит для многих случаев, когда вы просто хотите передать фиксированную строку в команду: просто проверьте, как оболочка будет интерпретировать строку в двойных кавычках через echo , и при необходимости экранировать символы обратной косой чертой.

В этом примере вы увидите, что двойных кавычек достаточно для защиты строки:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'

4

Очевидно, что было бы проще просто заключить в двойные кавычки, но где в этом проблема? Вот ответ, используя только одинарные кавычки. Я использую переменную вместо того, aliasчтобы ее было легче распечатать для доказательства, но это то же самое, что и использовать alias.

$ rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'
$ echo $rxvt
urxvt -fg '#111111' -bg '#111111'

объяснение

Ключ в том, что вы можете закрыть одинарную кавычку и открыть ее столько раз, сколько захотите. Например так foo='a''b'же, как foo='ab'. Таким образом, вы можете закрыть одинарную кавычку, добавить буквальную одинарную кавычку \', а затем снова открыть следующую одинарную кавычку.

Диаграмма пробоя

Эта диаграмма проясняет использование скобок, чтобы показать, где одинарные кавычки открываются и закрываются. Кавычки не являются «вложенными», как круглые скобки. Также можно обратить внимание на цветовую подсветку, которая правильно применяется. Строки в кавычках - бордовые, тогда как \'черные.

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'    # original
[^^^^^^^^^^] ^[^^^^^^^] ^[^^^^^] ^[^^^^^^^] ^    # show open/close quotes
 urxvt -fg   ' #111111  '  -bg   ' #111111  '    # literal characters remaining

(По сути, это тот же ответ, что и у Адриана, но я чувствую, что это объясняет это лучше. Кроме того, его ответ содержит 2 лишних одинарных кавычки в конце.)


+1 за использование '\''метода, который я рекомендую, за '"'"'метод, который людям труднее читать.
mtraceur

3

Вот разработка «Единого верного ответа», на которую мы ссылаемся выше:

Иногда я буду скачивать с помощью rsync через ssh, и мне придется экранировать имя файла с '' ДВАЖДЫ! (OMG!) Один раз для Bash и один раз для SSH. Тот же принцип чередования разделителей цитат работает здесь.

Например, допустим, мы хотим получить: «Истории Лос-Анджелеса Теру» ...

  1. Сначала вы заключаете Луи Теру в одинарные кавычки для bash и двойные кавычки для ssh: «Луи Теру»
  2. Затем вы используете одинарные кавычки, чтобы избежать двойной кавычки '"'
  3. Использование двойных кавычек, чтобы избежать апострофа "'"
  4. Затем повторите # 2, используя одинарные кавычки, чтобы избежать двойной кавычки '"'
  5. Затем заключите LA Stories в одинарные кавычки для bash и двойные кавычки для ssh: '"LA Stories"'

И вот! Вы заканчиваете с этим:

rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'

что очень много работы для одного маленького »- но вы идете


3
shell_escape () {
    printf '%s' "'${1//\'/\'\\\'\'}'"
}

Объяснение реализации:

  • двойные кавычки, чтобы мы могли легко выводить перенос одинарных кавычек и использовать ${...}синтаксис

  • Поиск и замена bash выглядит так: ${varname//search/replacement}

  • мы заменяем 'на'\''

  • '\''кодирует один, 'например, так:

    1. ' заканчивает одинарное цитирование

    2. \'кодирует '(обратная косая черта необходима, потому что мы не внутри кавычек)

    3. ' снова запускается одинарное цитирование

    4. bash автоматически объединяет строки без пробелов между

  • есть \перед каждым \и 'потому что это правила выхода ${...//.../...}.

string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded:  $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"

PS Всегда кодируйте строки в одинарных кавычках, потому что они проще, чем строки в двойных кавычках.


2

Другой способ решить проблему слишком большого количества слоев вложенной цитаты:

Вы пытаетесь втиснуть слишком много в слишком маленькое пространство, поэтому используйте функцию bash.

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

lets_do_some_stuff() {
    tmp=$1                       #keep a passed in parameter.
    run_your_program $@          #use all your passed parameters.
    echo -e '\n-------------'    #use your single quotes.
    echo `date`                  #use your back ticks.
    echo -e "\n-------------"    #use your double quotes.
}
alias foobarbaz=lets_do_some_stuff

Затем вы можете использовать переменные $ 1 и $ 2, а также одинарные, двойные кавычки и обратные тики, не беспокоясь о том, что функция псевдонима нарушит их целостность.

Эта программа печатает:

el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385
alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------

2

Если у вас установлен GNU Parallel, вы можете использовать его внутреннее цитирование:

$ parallel --shellquote
L's 12" record
<Ctrl-D>
'L'"'"'s 12" record'
$ echo 'L'"'"'s 12" record'
L's 12" record

С версии 20190222 вы можете даже --shellquoteнесколько раз:

$ parallel --shellquote --shellquote --shellquote
L's 12" record
<Ctrl-D>
'"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
$ eval eval echo '"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
L's 12" record

Он будет указывать строку во всех поддерживаемых оболочках (не только bash).


1

Эта функция:

quote () 
{ 
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

позволяет цитировать 'внутри '. Используйте как это:

$ quote "urxvt -fg '#111111' -bg '#111111'"
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

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

#!/bin/bash

quote ()
{
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

while read line; do
    quote "$line"
done <<-\_lines_to_quote_
urxvt -fg '#111111' -bg '#111111'
Louis Theroux's LA Stories
'single quote phrase' "double quote phrase"
_lines_to_quote_

Будет выводить:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
'Louis Theroux'\''s LA Stories'
''\''single quote phrase'\'' "double quote phrase"'

Все правильно цитируемые строки внутри одинарных кавычек.


1

Если вы генерируете строку оболочки в Python 2 или Python 3, следующее может помочь процитировать аргументы:

#!/usr/bin/env python

from __future__ import print_function

try:  # py3
    from shlex import quote as shlex_quote
except ImportError:  # py2
    from pipes import quote as shlex_quote

s = """foo ain't "bad" so there!"""

print(s)
print(" ".join([shlex_quote(t) for t in s.split()]))

Это выведет:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'

1

Вот мои два цента - в случае, если кто-то хочет быть sh-портативным, а не просто bash-специфичным (хотя решение не слишком эффективно, поскольку оно запускает внешнюю программу - sed):

  • поместите это в quote.sh(или просто quote) где-нибудь на вашем PATH:
# это работает со стандартным вводом (stdin)
цитата () {
  echo -n "'";
  sed 's / \ ([' "'"'] ['"'" '] * \) /' "'"' "\ 1" '"'" '/ g';
  echo -n "'"
}

дело "$ 1" в
 -) цитата ;;
 *) echo "using: cat ... | quote - # ввод одинарных кавычек для оболочки Bourne" 2> & 1 ;;
ESAC

Пример:

$ echo -n "Gaday, приятель!" | ./quote.sh -
'G' '' '' день, приятель! '

И, конечно же, это преобразует обратно:

$ echo 'G' "'"' день, приятель! '
Привет, приятель!

Объяснение: в основном мы должны заключить входные данные в кавычки ', а затем также заменить любую одиночную кавычку внутри этого микро-монстра: '"'"'(завершите вводную кавычку парным соединением ', экранируйте найденную одинарную кавычку, заключив ее в двойные кавычки - "'", и затем , наконец , выпустить новый открывающий апостроф ', или в псевдо-обозначениях: ' + "'" + ' == '"'"')

Один из стандартных способов сделать это - использовать sedследующую команду подстановки:

s/\(['][']*\)/'"\1"'/g 

Одна небольшая проблема заключается в том, что для использования этого в оболочке необходимо экранировать все эти символы одинарных кавычек в самом выражении sed - что приводит к чему-то вроде

sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' 

(И один хороший способ построить этот результат - передать исходное выражение s/\(['][']*\)/'"\1"'/gсценариям Кайла Роуза или Джорджа В. Рейли).

Наконец, имеет смысл ожидать, что входные данные будут поступать stdin- поскольку передача его через аргументы командной строки может быть уже слишком большой проблемой.

(О, и может быть, мы хотим добавить небольшое справочное сообщение, чтобы скрипт не зависал, когда кто-то просто запускает его, ./quote.sh --helpзадаваясь вопросом, что он делает.)


0

Вот еще одно решение. Эта функция будет принимать один аргумент и соответствующим образом заключать его в кавычки, используя символ одинарных кавычек, так же, как объясняется в ответе с голосованием выше:

single_quote() {
  local quoted="'"
  local i=0
  while [ $i -lt ${#1} ]; do
    local ch="${1:i:1}"
    if [[ "$ch" != "'" ]]; then
      quoted="$quoted$ch"
    else
      local single_quotes="'"
      local j=1
      while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; do
        single_quotes="$single_quotes'"
        ((j++))
      done
      quoted="$quoted'\"$single_quotes\"'"
      ((i+=j-1))
    fi
    ((i++))
  done
  echo "$quoted'"
}

Итак, вы можете использовать это так:

single_quote "1 2 '3'"
'1 2 '"'"'3'"'"''

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.