Як уникнути одинарних лапок у межах одного цитованого рядка


1016

Скажімо, у вас є 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), тому підміна подвійних лапок, коли виникає проблема з одинарними лапками, НЕ є хорошим рішенням. Подвійні лапки задають змінні $ ... змінні підлягають заміні перед виконанням, тоді як одинарні лапки задають $ ... слід розглядати буквально.
Чак Колларс

Якщо ви думаєте, я використав подвійні лапки, але це все ще не працює , надрукуйте свій сценарій знову.
Samy Bencherif

Відповіді:


1453

Якщо ви дійсно хочете використовувати одиничні лапки в самому зовнішньому шарі, пам’ятайте, що ви можете склеїти обидва типи цитат. Приклад:

 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_ Що '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

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


Я часто підхоплюю функцію "котирування" у своїх сценаріях Perl, щоб зробити це для мене. Етапи:

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

Це в значній мірі піклується про всі справи.

Життя стає веселішим, коли ви вводите evalв свої сценарії оболонки. Ви, по суті, повинні знову все процитувати!

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

8
@nicerobot: Я додав приклад, який показує, що: 1) Я не намагаюся уникнути лапок в межах одного типу цитат, 2) ні в альтернативних котируваннях; 3) Perl використовується для автоматизації процесу генерації дійсних bash string, що посилаються на вкладені цитати
Адріан Пронк

18
Перший абзац сам по собі - це відповідь, яку я шукав.
Дейв Косі

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

195

Оскільки синтаксис Bash 2.04$'string' (замість просто 'string'; попередження: не плутати з $('string')) - це ще один механізм котирування, який дозволяє 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. Послідовності втечі зворотного схилу, якщо вони є, декодуються наступним чином:

    \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 як рядки на wiki bash-hackers.org. Також зауважте, що у файлі "Bash Changes" ( огляд тут ) багато згадується про зміни та виправлення помилок, пов'язані з $'string'механізмом цитування.

За даними unix.stackexchange.com Як використовувати спеціальний символ як звичайний? він повинен працювати (з деякими варіантами) у bash, zsh, mksh, ksh93 та FreeBSD та busybox sh.


може бути використаний, але рядок, що цитується тут, не є справжньою цитованою, вміст цього рядка може бути інтерпретований оболонкою: echo $'foo\'b!ar'=> !ar': event not found
regilero

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

1
Так, це причина "може", я мав це на червоному капелюсі 6.4, звичайно, старшій баш-версії.
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 :

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

так що Баш не зрозуміє:

alias x='y \'z '

однак ви можете це зробити, якщо оточуєте подвійні лапки:

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


Вміст, укладений у подвійні лапки, оцінюється, тому розміщення лише одиничних лапок у подвійних лапках, як запропонував Ліорі, здається правильним рішенням.
Пьотр Доброгост

3
Це фактична відповідь на питання. Хоча прийнята відповідь може забезпечити рішення, вона технічно відповідає на питання, яке не було задано.
Матвій Г

3
Метью, питання полягало в тому, щоб уникнути одинарних цитат в межах однієї цитати. Ця відповідь просить користувача змінити свою поведінку, і якщо у вас є перешкода використовувати подвійні лапки (як підказує заголовок питання), ця відповідь не допоможе. Хоча це дуже корисно (хоч і очевидно), і як таке заслуговує на підсумок, але прийнята відповідь стосується точної проблеми, про яку опитували Op.
Фернандо Кордейро

Не потрібно цитувати єдину цитату в подвійному рядку цитат.
Меттью Д. Скоулфілд

32

Я можу підтвердити, що використання '\''для однієї цитати всередині рядка з одним цитуванням працює в Bash, і це можна пояснити так само, як аргумент "приклеювання" з попередньої теми. Припустимо, у нас є рядок, що цитується: 'A '\''B'\'' C'(усі цитати тут є одинарними цитатами). Якщо він передається відлуння, він друкує наступне: A 'B' C. У кожному '\''першому цитаті закривається поточний одноцитуваний рядок, наступні \'приклеюють одну цитату до попереднього рядка ( \'це спосіб вказати одну цитату без запуску рядка з цитатами), а остання цитата відкриває ще один рядок з цитатами.


2
Це вводить в оману, цей синтаксис '\' 'не входить "всередину" жодного цитованого рядка. У цьому вислові "A" \ "B" \ "" C "ви об'єднуєте 5 \ escape та рядки одинарних лапок
tekpapaul

1
@teknopaul Присвоєння alias something='A '\''B'\'' C'призводить до somethingтого, що це одна рядок, тому навіть якщо права частина завдання технічно не є єдиною струною, я не думаю, що це має велике значення.
Teemu Leisti

У той час як це працює в вашому прикладі, це не технічно забезпечує рішення для того, як вставити апостроф всередині одного рядка в лапках. Ви вже пояснили це, але так, це робиться 'A ' + ' + 'B' + ' + ' C'. Іншими словами, рішення для вставки символів однієї цитати всередині однорядкового рядка повинно дозволити мені створити такий рядок сам і роздрукувати його. Однак це рішення не спрацює в цьому випадку. STR='\''; echo $STR. За задумом, BASH цього по-справжньому не дозволяє.
krb686

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

20

Обидві версії працюють, як з конкатенацією, використовуючи уникнутий символ єдиної цитати (\ '), так і з конкатенацією, додаючи символ подвійної лапки у подвійні лапки ("'").

Автор запитання не зауважив, що в кінці останньої спроби втечі була додаткова єдина цитата:

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

Як видно з попереднього приємного твору мистецтва ASCII / Unicode, за останньою єдиною цитатою (\ '), що уникнув, супроводжується непотрібною єдиною цитатою ('). Використання підсвічувача синтаксису, як у присутній у Блокноті ++, може виявитися дуже корисним.

Те саме стосується іншого прикладу, як наступного:

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 :)
php-dev

16

Простий приклад скасування цитат у оболонці:

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

Це робиться, закінчивши вже відкритий один ( '), розмістивши втечене ( \'), потім відкривши ще одне ( '). Цей синтаксис працює для всіх команд. Це дуже схожий підхід до 1-ї відповіді.


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
Я намагався вкласти щось в рамках однієї цитати, яка була в подвійних лапках, які, в свою чергу, були в одинарних лапках. Yikes. Дякую за відповідь "спробуйте інший підхід". Це змінило значення.
Клінтон Блекмор

1
Мені запізнюється на 5 років, але ви не пропустили жодної цитати в останньому псевдонімі?
Жульєн

1
@Julien Я не бачу проблем ;-)
nicerobot

11

Оскільки не можна ставити окремі лапки в межах рядків, що цитуються, найпростішим і найчитабельнішим варіантом є використання рядка HEREDOC

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

alias rxvt=$command

У наведеному вище коді HEREDOC надсилається catкоманді, вихід якої присвоюється змінній через позначення заміни команди$(..)

Поставити єдину цитату навколо HEREDOC потрібно, оскільки вона знаходиться в межах $()


Я хотів би, щоб я прокрутився так далеко раніше - я придумав цей підхід і прийшов сюди, щоб його опублікувати! Це набагато чіткіше і легше для читання, ніж усі інші підходи до втечі. Це не буде працювати з деякими неошифрованими оболонками, такими як dashоболонка за замовчуванням у запущених сценаріях Ubuntu та інших місцях.
Корній

Дякую! що те, що я шукав, спосіб визначити команду як через heredoc і передати команду автоматично уникнув в ssh. BTW cat << КОМАНДА без лапок дозволяє інтерполювати вивідні файли всередині команди і також працює для цього підходу.
Ігор Твердовський

10

Я просто використовую коди оболонок .. наприклад, \x27або \\x22як це застосовується. Жодних клопотів, ніколи насправді.


Чи можете ви показати приклад цього в роботі? Для мене це просто друкує буквальне x27слово (на Centos 6.6)
Буде Шеппард

6

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

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. shell-quotes 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це лише послідовність рядків, котируваних окремо, які потім не інтерпретуються як оператори, що легко зрозуміти, чи повторюєте ви рядок, який би передані в баш:

$ (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 що вимагає використання одиничних цитат.

Обидві наступні роботи в баш.

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

У наведеному прикладі просто використовуються подвійні лапки замість одинарних лапок в якості зовнішнього механізму втечі:

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!) Раз за баш та один раз за сш. Тут діє той самий принцип чергування розмежувачів котирувань.

Наприклад, скажімо, що ми хочемо отримати: LA Stories Луї Теру ...

  1. Спочатку ви додаєте Louis Theroux в одинарні лапки для bash та подвійні цитати для ssh: '"Louis Theroux"'
  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)
quote () {
  відлуння -n "'";
  sed 's / \ ([' "'"'] ['"" "] * \) /" "" "" \ 1 "" "" "/ g';
  echo -n "'"
}

випадок "$ 1" в
 -) цитата ;;
 *) echo "використання: cat ... | quota - # введення з однією котировкою для оболонки Борна" 2> & 1 ;;
есак

Приклад:

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

І, звичайно, це перетворює назад:

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