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

Допустим, у вас есть Баш 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'\''

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

25 ответов

Решение

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

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

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

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

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

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


Я часто использую функцию "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'\''\'\'''\'''\'''

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

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

  $> 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. Экранирующие последовательности с обратной косой чертой, если они есть, декодируются следующим образом:

    \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.

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

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

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

alias x='y \'z '

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

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

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

Как избежать одинарных кавычек () и двойных кавычек () с помощью шестнадцатеричных и восьмеричных символов

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

Вот некоторые примеры:

1. Пример одинарной кавычки, где 'экранируется шестнадцатеричным кодом \x27или восьмеричный \047(соответствующий ему ):

      # 1. hex
$ echo -e "Let\x27s get coding!"
Let's get coding!

# 2. octal
$ echo -e "Let\047s get coding!"
Let's get coding!

2. Пример двойной кавычки, где "экранируется шестнадцатеричным кодом \x22или восьмеричный \042(соответствующий ему ).

Примечание: bashэто чокнутый! Иногда даже символ имеет особое значение , и его нужно либо удалить из двойных кавычек, а затем экранировать "like this"\! или полностью заключить в одинарные кавычки 'like this!', а не в двойных кавычках.

      # 1. hex; escape `!` by removing it from within the double quotes 
# and escaping it with `\!`
$ echo -e "She said, \x22Let\x27s get coding"\!"\x22"
She said, "Let's get coding!"

# OR put it all within single quotes:
$ echo -e 'She said, \x22Let\x27s get coding!\x22'
She said, "Let's get coding!"


# 2. octal; escape `!` by removing it from within the double quotes 
$ echo -e "She said, \042Let\047s get coding"\!"\042"
She said, "Let's get coding!"

# OR put it all within single quotes:
$ echo -e 'She said, \042Let\047s get coding!\042'
She said, "Let's get coding!"


# 3. mixed hex and octal, just for fun
# escape `!` by removing it from within the double quotes when it is followed by
# another escape sequence
$ echo -e "She said, \x22Let\047s get coding! It\x27s waaay past time to begin"\!"\042"
She said, "Let's get coding! It's waaay past time to begin!"

# OR put it all within single quotes:
$ echo -e 'She said, \x22Let\047s get coding! It\x27s waaay past time to begin!\042'
She said, "Let's get coding! It's waaay past time to begin!"

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

      $ echo -e "She said, \x22Let\047s get coding! It\x27s waaay past time to begin!\042"
bash: !\042: event not found

ИЛИ ЖЕ:

      $ echo -e "She said, \x22Let\x27s get coding!\x22"
bash: !\x22: event not found

Рекомендации:

  1. код ASCIIhttps://en.wikipedia.org/wiki/ASCII#Printable_charactersкод ASCII
  2. https://serverfault.com/questions/208265/what-is-bash-event-not-found/208266#208266
  3. См. Также мой другой ответ здесь: Как мне писать символы, отличные от ASCII, с помощью эха?.

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

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

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

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

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

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

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

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'"

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

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

Невозможно избежать одинарных кавычек или чего-либо еще в одинарных кавычках.

Ниже приводится, как ни странно, полная команда:

      $ echo '\'

выход которого:

      \

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

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

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

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

alias rxvt=$command

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

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

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

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

Если мы предполагаем, что мы используем 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"

Большинство из этих ответов затрагивают конкретный случай, о котором вы спрашиваете. Существует общий подход, который мы с другом разработали и который позволяет произвольно заключать в кавычки, если вам нужно процитировать команды bash через несколько уровней расширения оболочки, например, через 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. кавычки каждый аргумент к внутреннему 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

В первом примере bash сразу расширяется quote_args cd /; pwd 1>&2 на две отдельные команды, quote_args cd / а также pwd 1>&2так что CWD еще /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

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

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

объяснение

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

Диаграмма разбивки

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

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

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

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

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

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

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

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

В дополнение к идеальному ответу @JasonWoof, я хочу показать, как я решил связанную проблему

в моем случае кодирование одинарных кавычек с '\'' не всегда будет достаточно, например, если строка должна быть заключена в одинарные кавычки, но общее количество кавычек дает нечетное количество

#!/bin/bash

# no closing quote
string='alecxs\'solution'

# this works for string
string="alecxs'solution"
string=alecxs\'solution
string='alecxs'\''solution'

предположим, что строка - это имя файла, и нам нужно сохранить имена файлов в кавычках в списке (например, stat -c% N./*> list)

echo "'$string'" > "$string"
cat "$string"

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

while read file
  do
    ls -l "$file"
    eval ls -l "$file"
done < "$string"

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

string="${string//$'\047'/\'\$\'\\\\047\'\'}"

# result
echo "$string"

теперь это работает, потому что котировки всегда сбалансированы

echo "'$string'" > list
while read file
  do
    ls -l "$file"
    eval ls -l "$file"
done < list

Надеюсь, это поможет при столкновении с подобной проблемой

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

Иногда я буду скачивать с помощью 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"'

что ужасно много работы для одного маленького "- но вы идете

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

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

case "$ 1" в
 -) цитата;;
 *) echo "использование: cat ... | quote - # ввод в одинарные кавычки для оболочки Bourne" 2> & 1;;
esac

Пример:

$ echo -n "Доброго времени суток, дружище!" |./quote.sh -
'G' "'"' день, приятель!'

И, конечно же, обратное преобразование:

$ echo 'G' "'"' день, дружище!'
Доброго времени суток, дружище!

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

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

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

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

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

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

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

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

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

Вы пытаетесь втиснуть слишком много в слишком маленькое пространство, поэтому используйте функцию 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
-------------
shell_escape () {
    echo -nE "'${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 Всегда кодируйте строки в одинарных кавычках, потому что они проще, чем строки в двойных кавычках.

Если у вас установлен 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).

Если вы генерируете строку оболочки в 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!'

Эта функция:

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"'

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

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

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'
Другие вопросы по тегам