Jak uniknąć pojedynczych cudzysłowów w ciągach pojedynczych cudzysłowów

1016

Powiedzmy, że masz Bash aliasjak:

alias rxvt='urxvt'

co działa dobrze.

Jednak:

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

nie będzie działać i nie będzie:

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

Więc jak skończysz dopasowywanie otwierających i zamykających cytatów w ciągu, gdy unikniesz cudzysłowów?

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

wydaje się niezręcznie, chociaż reprezentowałby ten sam ciąg, gdybyś mógł je w taki sposób połączyć.

Cons
źródło
16
Czy zdajesz sobie sprawę, że nie musisz używać pojedynczych cudzysłowów jako aliasu? Podwójne cytaty są znacznie łatwiejsze.
teknopaul
3
Zagnieżdżone podwójne cudzysłowy można wstawiać, "\""dlatego należy je stosować zamiast odpowiedzi @ liori, gdy tylko jest to możliwe.
alan
7
Podwójne cudzysłowy zachowują się zupełnie inaczej niż pojedyncze cudzysłowy w * nix (w tym Bash i powiązane narzędzia, takie jak Perl), więc zastępowanie podwójnych cudzysłowów, gdy występuje problem z pojedynczymi cudzysłowami, NIE jest dobrym rozwiązaniem. Podwójne cudzysłowy określają zmienne $ ... przed wykonaniem, a pojedyncze cudzysłowy określają $ ... należy traktować dosłownie.
Chuck Kollars
Jeśli myślisz, użyłem podwójnych cudzysłowów, ale nadal nie działa , zrób ponownie skrypt.
Samy Bencherif,

Odpowiedzi:

1453

Jeśli naprawdę chcesz używać pojedynczych cudzysłowów w najbardziej zewnętrznej warstwie, pamiętaj, że możesz przykleić oba rodzaje cudzysłowów. Przykład:

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

Wyjaśnienie, jak '"'"'należy interpretować to jako ':

  1. ' Zakończ pierwszą ofertę, która używa pojedynczych ofert.
  2. " Rozpocznij drugą ofertę, używając podwójnych cudzysłowów.
  3. ' Cytowana postać.
  4. " Zakończ drugi cytat, używając podwójnych cudzysłowów.
  5. ' Rozpocznij trzecią ofertę, używając pojedynczych ofert.

Jeśli nie umieścisz żadnych białych znaków między (1) i (2) lub między (4) i (5), powłoka zinterpretuje ten ciąg jako jedno długie słowo.

liori
źródło
5
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"Działa, gdy w ciągu aliasu znajdują się zarówno pojedyncze cudzysłowy, jak i podwójne cudzysłowy!
Uphill_ What '1
17
Moja interpretacja: bash domyślnie łączy wyrażenia łańcuchowe o różnych cytatach.
Benjamin Atkin,
2
pracował dla mnie, przykład podwójnych alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
znaków
2
Z pewnością nie jest to najbardziej czytelne rozwiązanie. Nadmiernie wykorzystuje pojedyncze cudzysłowy tam, gdzie tak naprawdę nie są potrzebne.
oberlies,
26
Twierdzę, że '\''jest znacznie bardziej czytelny w większości kontekstów niż '"'"'. W rzeczywistości pierwsza z nich jest prawie zawsze wyraźnie wyraźna w ciągu pojedynczego cudzysłowu, a zatem jest tylko kwestią przyporządkowania go semantycznie do znaczenia „jest to cytowany znak ucieczki”, tak jak \"w przypadku ciągów podwójnych cudzysłowów. Podczas gdy ten ostatni łączy się w jedną linię cytatów i w wielu przypadkach wymaga dokładnej kontroli, aby właściwie rozróżnić.
mtraceur
263

Zawsze po prostu zastępuję każdy osadzony pojedynczy cytat sekwencją: '\''(to znaczy: quote backslash quote quote), która zamyka ciąg, dołącza do niego pojedynczy cytat i otwiera go ponownie.


Często używam funkcji cytowania w moich skryptach Perla, aby to dla mnie zrobić. Kroki byłyby następujące:

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

To właściwie zajmuje się wszystkimi sprawami.

Życie staje się przyjemniejsze po wprowadzeniu evaldo skryptów powłoki. Zasadniczo musisz ponownie wszystko wycenić!

Na przykład utwórz skrypt Perla o nazwie quote zawierający powyższe instrukcje:

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

następnie użyj go, aby wygenerować poprawnie cytowany ciąg:

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

wynik:

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

które można następnie skopiować / wkleić do polecenia aliasu:

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

(Jeśli musisz wstawić polecenie do eval, uruchom cytat ponownie:

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

wynik:

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

które można skopiować / wkleić w eval:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''
Adrian Pronk
źródło
1
Ale to nie jest perl. I jak wskazał powyżej Steve B, w swoim odwołaniu do „podręcznika GNU odniesienia”, nie można uciec od cytatów w bash w ramach tego samego rodzaju cytatu. I w rzeczywistości nie trzeba uciec przed nimi w alternatywnych cudzysłowach, np. „” Jest prawidłowym ciągiem pojedynczego cudzysłowu, a „” ”jest prawidłowym ciągiem podwójnego cudzysłowu bez konieczności ucieczki.
nicerobot
8
@nicerobot: Dodałem przykład pokazujący, że: 1) Nie próbuję uciec cytatów w ramach tego samego rodzaju cytatu, 2) ani alternatywnych cytatów, i 3) Perl jest używany do automatyzacji procesu generowania poprawnego ciąg bash zawierający osadzone cytaty
Adrian Pronk
18
Pierwszy akapit sam w sobie jest odpowiedzią, której szukałem.
Dave Causey
9
To samo robi bash, wpisz set -xi echo "here's a string"zobaczysz, że bash się wykonuje echo 'here'\''s a string'. ( set +xaby przywrócić normalne zachowanie)
arekolek
195

Ponieważ składnia Bash 2.04$'string' (zamiast po prostu 'string'; ostrzeżenie: nie mylić z $('string')) jest kolejnym mechanizmem cytowania, który pozwala na sekwencje specjalne ANSI podobne do C i umożliwia rozszerzenie do wersji pojedynczego cytatu.

Prosty przykład:

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

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

W Twoim przypadku:

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

Typowe sekwencje specjalne działają zgodnie z oczekiwaniami:

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

Poniżej znajduje się kopia + wklejona powiązana dokumentacja z man bash(wersja 4.4):

Słowa w postaci $ „string” są traktowane specjalnie. Słowo jest interpretowane jako ciąg znaków, a znaki specjalne z odwrotnym ukośnikiem są zastępowane zgodnie ze standardem ANSI C. Sekwencje specjalne odwrotnego ukośnika, jeśli występują, są dekodowane w następujący sposób:

    \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

Rozwinięty wynik jest cytowany pojedynczo, tak jakby znak dolara nie był obecny.


Zobacz cytaty i ucieczkę: ciągi ANSI C na wiki bash-hackers.org, aby uzyskać więcej informacji. Zwróć też uwagę, że plik „Bash Changes” ( tutaj omówienie ) wiele wspomina o zmianach i poprawkach błędów związanych z $'string'mechanizmem cytowania.

Według unix.stackexchange.com Jak używać znaku specjalnego jako normalnego? powinien działać (z pewnymi odmianami) w bash, zsh, mksh, ksh93 oraz FreeBSD i busybox sh.

mj41
źródło
może być użyty, ale pojedynczy ciąg cytowany tutaj nie jest prawdziwym pojedynczym cytatem, treść tego łańcucha może być interpretowana przez powłokę: echo $'foo\'b!ar'=> !ar': event not found
regilero
2
Na mojej maszynie > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
mj41,
1
Tak, to jest powód „może”, miałem go na Red Hat 6.4, na pewno starszej wersji bash.
regilero
Bash ChangeLog zawiera wiele poprawek błędów, $'więc prawdopodobnie najłatwiejszym sposobem jest wypróbowanie go na starszych systemach.
mj41
pamiętaj: pochodzie. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions. z tiswww.case.edu/php/chet/bash/CHANGES . Nadal działa w wersji 4.3.42, ale nie w wersji 4.3.48.
stiller_leser
49

Nie widzę wpisu na jego blogu (link pls?), Ale zgodnie z podręcznikiem GNU :

Umieszczanie znaków w pojedynczych cudzysłowach („”) zachowuje dosłowną wartość każdego znaku w cudzysłowie. Pojedynczy cytat może nie wystąpić między pojedynczymi cudzysłowami, nawet jeśli poprzedzony jest odwrotnym ukośnikiem.

więc bash nie zrozumie:

alias x='y \'z '

możesz to jednak zrobić, jeśli otaczasz się podwójnymi cudzysłowami:

alias x="echo \'y "
> x
> 'y
Steve B.
źródło
Zawartość zamknięta z podwójnymi cudzysłowami jest oceniana, więc umieszczenie tylko pojedynczych cudzysłowów w podwójnych cudzysłowach, jak sugeruje liori, wydaje się właściwym rozwiązaniem.
Piotr Dobrogost
3
To jest faktyczna odpowiedź na pytanie. Chociaż zaakceptowana odpowiedź może stanowić rozwiązanie, to technicznie odpowiada na pytanie, które nie zostało zadane.
Matthew G
3
Matthew, pytanie dotyczyło unikania pojedynczych cytatów w pojedynczych cytatach. Ta odpowiedź prosi użytkownika o zmianę zachowania, a jeśli masz przeszkodę w stosowaniu podwójnych cudzysłowów (jak sugeruje tytuł pytania), ta odpowiedź nie pomogłaby. Jest to jednak dość przydatne (choć oczywiste) i jako takie zasługuje na aprobatę, ale przyjęta odpowiedź rozwiązuje dokładnie problem, o który pytała Op.
Fernando Cordeiro,
Nie ma potrzeby cytowania pojedynczego cytatu w ciągu podwójnego cytatu.
Matthew D. Scholefield,
32

Mogę potwierdzić, że użycie '\''pojedynczego cudzysłowu w ciągu pojedynczego cudzysłowu działa w języku Bash i można to wyjaśnić w taki sam sposób, jak argument „sklejanie” z wcześniejszego wątku. Załóżmy, że mamy ciąg cudzysłowu: 'A '\''B'\'' C'(wszystkie cytaty tutaj są pojedynczymi cudzysłowami). Jeśli jest ona przekazywana do echo, drukuje, co następuje: A 'B' C. W każdym '\''pierwszym cytacie zamyka bieżący ciąg pojedynczego cudzysłowu, poniższy \'przykleja pojedynczy cytat do poprzedniego ciągu ( \'jest to sposób na określenie pojedynczego cytatu bez rozpoczynania cytowanego ciągu), a ostatni cytat otwiera kolejny ciąg pojedynczego cudzysłowu.

Michaił w YugaByte
źródło
2
Jest to mylące, ta składnia „\” nie wchodzi w „ciąg” pojedynczego cudzysłowu. W tym stwierdzeniu „A” \ „B” \ „C” łączysz łańcuchy znaków ucieczki i pojedynczych cudzysłowów
teknopaul
1
@teknopaul Przypisanie alias something='A '\''B'\'' C'powoduje, somethingże jest to pojedynczy ciąg, więc nawet jeśli prawa strona zadania nie jest technicznie pojedynczym ciągiem, nie sądzę, żeby miało to duże znaczenie.
Teemu Leisti,
Chociaż działa to w twoim przykładzie, technicznie nie zapewnia rozwiązania, w jaki sposób wstawić pojedynczy cytat w ciągu pojedynczego cudzysłowu. Już to wyjaśniłeś, ale tak, to działa 'A ' + ' + 'B' + ' + ' C'. Innymi słowy, rozwiązanie polegające na wstawianiu znaków pojedynczego cudzysłowu w ciągu pojedynczego cudzysłowu powinno pozwolić mi stworzyć taki ciąg i wydrukować go. Jednak to rozwiązanie nie będzie działać w tym przypadku. STR='\''; echo $STR. Zgodnie z projektem BASH tak naprawdę na to nie pozwala.
krb686,
@mikhail_b, tak, '\''działa na bash. Czy możesz wskazać, które sekcje gnu.org/software/bash/manual/bashref.html określają takie zachowanie?
Jingguo Yao
20

Obie wersje działają albo z konkatenacją przy użyciu znaku ucieczki pojedynczego cudzysłowu (\ '), albo z konkatenacją poprzez umieszczenie znaku pojedynczego cudzysłowu w podwójnych cudzysłowach („” ”).

Autor pytania nie zauważył, że na końcu ostatniej próby ucieczki był dodatkowy pojedynczy cytat ('):

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

Jak widać na poprzednim ładnym dziele ASCII / Unicode, po ostatnim cytowanym pojedynczym cudzysłowie (\ ') następuje niepotrzebny pojedynczy cudzysłów ('). Bardzo pomocne może być użycie wyróżniacza składni, takiego jak ten obecny w Notepad ++.

To samo dotyczy innego przykładu, takiego jak następujący:

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

Te dwa piękne przykłady aliasów pokazują w bardzo skomplikowany i zaciemniony sposób, w jaki sposób plik może zostać wyrównany. Oznacza to, że z pliku zawierającego wiele linii otrzymujesz tylko jedną linię z przecinkami i spacjami między zawartością poprzednich linii. Aby zrozumieć poprzedni komentarz, poniżej podano przykład:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214
po lewej stronie
źródło
3
Ulepszony dla ilustracji tabeli ASCII :)
php-dev
16

Prosty przykład ucieczki cytatów w powłoce:

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

Odbywa się to poprzez ukończenie już otwartego ( '), umieszczenie klawisza Esc ( \'), a następnie otwarcie kolejnego (' ). Ta składnia działa dla wszystkich poleceń. To bardzo podobne podejście do pierwszej odpowiedzi.

kenorb
źródło
15

Nie zajmuję się konkretnie kwestią cytowania, ponieważ czasami rozsądne jest rozważenie alternatywnego podejścia.

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

który możesz następnie wywołać jako:

rxvt 123456 654321

Chodzi o to, że możesz teraz alias to bez obawy o cytaty:

alias rxvt='rxvt 123456 654321'

lub, jeśli #z jakiegoś powodu musisz uwzględnić we wszystkich połączeniach:

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

który możesz następnie wywołać jako:

rxvt '#123456' '#654321'

wtedy alias to:

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

(ups, myślę, że w pewnym sensie zająłem się cytowaniem :)

nicerobot
źródło
1
Próbowałem umieścić coś w pojedynczych cudzysłowach, które były w podwójnych cudzysłowach, które z kolei były w pojedynczych cudzysłowach. Yikes. Dziękujemy za odpowiedź „spróbuj innego podejścia”. To zrobiło różnicę.
Clinton Blackmore,
1
Spóźniłem się 5 lat, ale czy nie brakuje ci ani jednego cytatu w ostatnim aliasie?
Julien
1
@Julien Nie widzę problemu ;-)
nicerobot
11

Ponieważ nie można umieszczać pojedynczych cudzysłowów w ciągach pojedynczych cudzysłowów, najprostszą i najbardziej czytelną opcją jest użycie łańcucha HEREDOC

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

alias rxvt=$command

W powyższym kodzie HEREDOC jest wysyłany do catpolecenia, a jego wynik jest przypisywany do zmiennej za pomocą notacji podstawienia polecenia$(..)

Konieczne jest umieszczenie pojedynczego cytatu w HEREDOC, ponieważ jest on w ciągu $()

Nerrve
źródło
Chciałbym wcześniej przewinąć w dół - wymyśliłem to podejście i przybyłem tutaj, aby je opublikować! Jest to o wiele czystsze i bardziej czytelne niż wszystkie inne metody ucieczki. Nie będzie to działać na niektórych powłokach innych niż bash, takich jak dashdomyślna powłoka w skryptach startowych Ubuntu i gdzie indziej.
Korny,
Dziękuję Ci! tego właśnie szukałem, sposobu zdefiniowania polecenia za pomocą heredoc i przekazania polecenia automatycznego ucieczki do ssh. BTW cat << COMMAND bez cudzysłowów pozwala interpolować zmienne wewnątrz polecenia i działa również w tym podejściu.
Igor Tverdovskiy
10

Po prostu używam kodów powłoki ... np. \x27Lub \\x22w stosownych przypadkach. Bezproblemowo, naprawdę.

Rob Jens
źródło
Czy możesz pokazać przykład tego działania? Dla mnie to po prostu drukuje literał x27(na Centos 6.6)
Will Sheppard
6

Większość z tych odpowiedzi dotyczy konkretnego przypadku, o który pytasz. Istnieje ogólne podejście, że przyjaciel i ja stworzyliśmy, która pozwala na arbitralne cytowanie w przypadku trzeba cytat bash polecenia za pośrednictwem wielu warstw powłoki, np ekspansji, poprzez ssh su -c, bash -citp Jest jeden rdzeń prymitywny trzeba tutaj w natywnej 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
}

Robi dokładnie to, co mówi: powtarza każdy argument osobno (oczywiście po rozszerzeniu basha):

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

Robi to oczywiste dla jednej warstwy rozszerzenia:

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

(Zauważ, że podwójne cudzysłowy $(quote_args ...)są konieczne, aby wynik był pojedynczym argumentem bash -c.) I można go bardziej ogólnie użyć do cytowania poprawnie poprzez wiele warstw rozwinięcia:

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

Powyższy przykład:

  1. shell cytuje każdy argument wewnętrznie quote_argsindywidualnie, a następnie łączy wynikowy wynik w pojedynczy argument z wewnętrznymi podwójnymi cudzysłowami.
  2. Shell notowania bash, -ci już po cytowane wynikiem z etapu 1, a następnie łączy się wynik w jeden argument z zewnętrznych cudzysłowach.
  3. wysyła ten bałagan jako argument na zewnątrz bash -c.

Taki jest pomysł w pigułce. Możesz robić z tym dość skomplikowane rzeczy, ale musisz uważać na kolejność oceny i na cytowane podciągi. Na przykład następujące czynności są niewłaściwe (w przypadku niektórych definicji „zła”):

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

W pierwszym przykładzie bash natychmiast rozwija się quote_args cd /; pwd 1>&2w dwie osobne komendy, quote_args cd /a pwd 1>&2więc CWD jest nadal w /tmptrakcie wykonywania pwdkomendy. Drugi przykład ilustruje podobny problem z globowaniem. Rzeczywiście ten sam podstawowy problem występuje w przypadku wszystkich rozszerzeń bash. Problem polega na tym, że podstawienie polecenia nie jest wywołaniem funkcji: dosłownie ocenia jeden skrypt bash i wykorzystuje jego dane wyjściowe jako część innego skryptu bash.

Jeśli spróbujesz po prostu uciec operatorom powłoki, nie powiedzie się, ponieważ przekazany ciąg znaków bash -cjest po prostu sekwencją pojedynczo cytowanych ciągów, które nie są następnie interpretowane jako operatory, co jest łatwe do zauważenia, jeśli powtórzy się ciąg zostały przekazane do 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'

Problem polega na tym, że przesadzasz. Potrzebne jest, aby operatory nie były cytowane jako dane wejściowe do załączania bash -c, co oznacza, że ​​muszą znajdować się poza $(quote_args ...)podstawianiem poleceń.

W związku z tym, co musisz zrobić w najbardziej ogólnym sensie, to zacytowanie powłoki każdego słowa polecenia, które nie jest przeznaczone do rozszerzenia w momencie podstawiania polecenia osobno, i nie stosuj żadnego dodatkowego cudzysłowu dla operatorów powłoki:

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

Gdy to zrobisz, cały ciąg jest uczciwą grą do dalszego cytowania na dowolnych poziomach oceny:

$ 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

itp.

Te przykłady mogą wydawać wymęczony zważywszy, że słowa takie jak success, sbini pwdnie trzeba być powłoka cytowany, ale kluczowy punkt, aby pamiętać przy pisaniu skryptu biorąc dowolny wejście jest, że chcesz zacytować wszystko nie jesteś absolutnie pewny robi” t trzeba przytoczyć, bo nigdy nie wiadomo, kiedy użytkownik będzie rzucać w Robert'; rm -rf /.

Aby lepiej zrozumieć, co dzieje się pod przykryciem, możesz bawić się przy pomocy dwóch małych funkcji pomocniczych:

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

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

, który wyliczy każdy argument do polecenia przed jego wykonaniem:

$ 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
Kyle Rose
źródło
Cześć Kyle. Twoje rozwiązanie pracował wielki dla przypadku miałem, kiedy musiałem przejść grupę argumentów jako pojedynczy argument: vagrant ssh -c {single-arg} guest. Te {single-arg}muszą być traktowane jako pojedynczy arg ponieważ włóczęga zajmuje następnego argumentu po nim, jak nazwa gościa. Nie można zmienić zamówienia. Ale musiałem przekazać polecenie i jego argumenty {single-arg}. Więc użyłem swojej quote_args()zacytować polecenia i jego argumenty, i umieścić w cudzysłowie wynik, i to działało jak czar: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Dzięki!!!
Andreas Maier
6

IMHO prawdziwą odpowiedzią jest to, że nie można uciec przed pojedynczymi cudzysłowami w ciągach pojedynczych cudzysłowów.

To niemożliwe.

Jeśli założymy, że używamy bash.

Z podręcznika 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.

Musisz użyć jednego z pozostałych mechanizmów zmiany ciągu znaków ”lub \

Nie ma w tym nic magicznego alias wymagałoby użycia pojedynczych cudzysłowów.

Obie poniższe prace działają w bashu.

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

Ten ostatni używa \ do zmiany znaku spacji.

W # 111111 nie ma też magii, która wymaga pojedynczych cudzysłowów.

Poniższe opcje osiągają ten sam wynik, co pozostałe dwie opcje, ponieważ alias rxvt działa zgodnie z oczekiwaniami.

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

Możesz także uciec od kłopotliwego # bezpośrednio

alias rxvt="urxvt -fg \#111111 -bg \#111111"
teknopaul
źródło
„prawdziwą odpowiedzią jest to, że nie można uciec przed pojedynczymi cudzysłowami w ciągach pojedynczych.” To technicznie prawda. Ale możesz mieć rozwiązanie, które zaczyna się od pojedynczego cytatu, kończy się pojedynczym cytatem i zawiera tylko pojedyncze cytaty w środku. stackoverflow.com/a/49063038
wisbucky
Nie przez ucieczkę, tylko przez konkatenację.
teknopaul
4

W podanym przykładzie po prostu użyłem podwójnych cudzysłowów zamiast pojedynczych cudzysłowów jako zewnętrznego mechanizmu zmiany znaczenia:

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

To podejście jest odpowiednie w wielu przypadkach, w których po prostu chcesz przekazać stały ciąg do polecenia: po prostu sprawdź, jak powłoka zinterpretuje ciąg cudzysłowu poprzez echo , jeśli to konieczne, znaki specjalne z odwrotnym ukośnikiem.

W tym przykładzie widać, że podwójne cudzysłowy są wystarczające do ochrony ciągu:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'
oberlies
źródło
4

Oczywiście łatwiej byłoby po prostu otoczyć podwójnymi cudzysłowami, ale w czym to wyzwanie? Oto odpowiedź przy użyciu tylko pojedynczych cudzysłowów. Używam zmiennej zamiast, aliaswięc łatwiej jest wydrukować dowód, ale to samo co przy użyciu alias.

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

Wyjaśnienie

Kluczem jest to, że możesz zamknąć pojedynczy cytat i otworzyć go tyle razy, ile chcesz. Na przykład foo='a''b'jest taki sam jak foo='ab'. Abyś mógł zamknąć pojedynczy cytat, dodać dosłowny pojedynczy cytat \', a następnie ponownie otworzyć następny pojedynczy cytat.

Schemat podziału

Ten schemat wyjaśnia, używając nawiasów kwadratowych, aby pokazać, gdzie pojedyncze cudzysłowy są otwierane i zamykane. Cytaty nie są „zagnieżdżone”, jak mogą być nawiasy. Możesz także zwrócić uwagę na prawidłowe podświetlanie kolorów. Cytowane struny są bordowe, a \'czarne.

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

(Jest to zasadniczo ta sama odpowiedź, co odpowiedź Adriana, ale wydaje mi się, że to wyjaśnia lepiej. Również jego odpowiedź zawiera 2 zbędne pojedyncze cytaty na końcu.)

wisbucky
źródło
+1 za użycie '\''metody, którą polecam, zamiast '"'"'metody, która często jest trudniejsza do odczytania przez ludzi.
mtraceur
3

Oto opracowanie na temat The One True Odpowiedź, o której mowa powyżej:

Czasami będę pobierać za pomocą rsync przez ssh i muszę uciec od nazwy pliku z 'DWUKROTNIE! (OMG!) Raz dla bash i raz dla ssh. Działa tutaj ta sama zasada naprzemiennego ograniczania cudzysłowów.

Na przykład, powiedzmy, że chcemy zdobyć: Historie LA Louisa Therouxa ...

  1. Najpierw umieść Louis Theroux w pojedynczych cudzysłowach dla bash i podwójnych cudzysłowach dla ssh: „„ Louis Theroux ””
  2. Następnie użyj pojedynczych cudzysłowów, aby uniknąć podwójnego cudzysłowu „”
  3. Użyj podwójnych cudzysłowów, aby uciec od apostrofu „” „
  4. Następnie powtórz # 2, używając pojedynczych cudzysłowów, aby uniknąć podwójnego cudzysłowu „”
  5. Następnie dołącz LA Stories do pojedynczych cytatów dla bash i podwójnych cytatów dla ssh: „LA Stories” ”

I oto! Skończysz z tym:

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

co jest strasznie dużo pracy dla jednego malucha ”- ale proszę bardzo

PatchyFog
źródło
3
shell_escape () {
    printf '%s' "'${1//\'/\'\\\'\'}'"
}

Wyjaśnienie wykonania:

  • podwójne cudzysłowy, dzięki czemu możemy łatwo wyprowadzać zawijanie pojedynczych cudzysłowów i używać ${...}składni

  • Wyszukiwanie i zamiana bash wygląda następująco: ${varname//search/replacement}

  • jesteśmy wymianie 'z'\''

  • '\''koduje taki singiel ':

    1. ' kończy pojedynczy cytat

    2. \'koduje a '(odwrotny ukośnik jest potrzebny, ponieważ nie znajdujemy się w cudzysłowach)

    3. ' ponownie rozpoczyna pojedynczy cytat

    4. bash automatycznie łączy łańcuchy bez odstępów między nimi

  • jest \przed wszystkim \i 'dlatego, że są to zasady ucieczki ${...//.../...}.

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

PS Zawsze koduje ciągi pojedynczego cudzysłowu, ponieważ są one znacznie prostsze niż ciągi podwójnie cytowane.

JasonWoof
źródło
2

Inny sposób rozwiązania problemu zbyt wielu warstw zagnieżdżonego cytatu:

Próbujesz wcisnąć za dużo w zbyt małą przestrzeń, więc użyj funkcji bash.

Problem polega na tym, że próbujesz mieć zbyt wiele poziomów zagnieżdżania, a podstawowa technologia aliasów nie jest wystarczająco silna, aby ją pomieścić. Użyj funkcji bash w ten sposób, aby pojedyncze, podwójne cudzysłowy zwrotne i przekazane parametry były obsługiwane normalnie, jak można by się spodziewać:

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

Następnie możesz użyć zmiennych 1 $ i 2 $ oraz pojedynczych, podwójnych cudzysłowów i tyknięć, nie martwiąc się o to, że funkcja aliasu psuje ich integralność.

Ten program drukuje:

el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385
alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------
Eric Leschinski
źródło
2

Jeśli masz zainstalowany GNU Parallel, możesz użyć jego wewnętrznego cytowania:

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

Od wersji 20190222 możesz nawet --shellquotewiele razy:

$ 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

Cytuje ciąg znaków we wszystkich obsługiwanych powłokach (nie tylko bash).

Ole Tange
źródło
1

Ta funkcja:

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

pozwala na cytowanie 'wnętrza '. Użyj tego:

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

Jeśli wiersz do cytowania staje się bardziej złożony, np. Podwójne cudzysłowy zmieszane z pojedynczymi cudzysłowami, uzyskanie ciągów do cytowania w zmiennej może być dość trudne. Kiedy pojawią się takie przypadki, napisz dokładny wiersz, który musisz zacytować w skrypcie (podobnie do tego).

#!/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_

Wyjdzie:

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

Wszystkie poprawnie cytowane ciągi znaków w pojedynczych cudzysłowach.


źródło
1

Jeśli generujesz ciąg powłoki w Pythonie 2 lub Pythonie 3, poniższe cytaty mogą pomóc w cytowaniu argumentów:

#!/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()]))

Spowoduje to:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'
George V. Reilly
źródło
1

Oto moje dwa centy - w przypadku, gdy ktoś chce być shprzenośny, a nie tylko bashspecyficzny (rozwiązanie nie jest jednak zbyt wydajne, ponieważ uruchamia program zewnętrzny - sed):

  • umieść to quote.sh(lub po prostu quote) gdzieś na swoim PATH:
# to działa ze standardowym wejściem (standardowe wejście)
quote () {
  echo -n "'";
  sed 's / \ ([' "'”'] ['”„ ””] * \) /' ”„ ”„ \ 1 ”„ ”„ ”/ g ';
  echo -n „”
}

przypadku „1 $” w
 -) cytat ;;
 *) echo „wykorzystanie: kot ... | quote - # wpisanie pojedynczych cudzysłowów dla powłoki Bourne'a 2> & 1 ;;
esac

Przykład:

$ echo -n "Dzień dobry, kolego!" | ./quote.sh -
Dzień „G”, „kolego!”

I to oczywiście przekształca:

$ echo „G” „” „dzień, kolego!”
Dzień dobry kolego!

Objaśnienie: w zasadzie musimy ująć dane wejściowe w cudzysłów ', a następnie zamienić dowolny pojedynczy cytat w tym mikrodźwięku: '"'"'(zakończ otwierający cytat parowaniem ', uniknij znalezionego pojedynczego cytatu, zawijając go podwójnymi cudzysłowami - "'"i wreszcie wydać nowe otwarcie pojedynczego cudzysłowu 'lub w pseudo-notacji: ' + "'" + ' == '"'"')

Jednym ze standardowych sposobów na wykonanie tego byłoby użycie sednastępującej komendy podstawiania:

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

Jednym małym problemem jest to, że aby użyć tego w powłoce, trzeba uciec od wszystkich tych pojedynczych cudzysłowów w samym wyrażeniu sed - co prowadzi do czegoś takiego jak

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

(a jednym dobrym sposobem na zbudowanie tego wyniku jest nakarmienie oryginalnego wyrażenia s/\(['][']*\)/'"\1"'/gskryptami Kyle'a Rose'a lub George'a V. Reilly'ego).

Wreszcie, sensowne jest oczekiwanie, że dane wejściowe będą pochodzić stdin- ponieważ przekazywanie ich przez argumenty wiersza poleceń może być już zbyt dużym problemem.

(Och, być może chcemy dodać mały komunikat pomocy, aby skrypt nie zawiesił się, gdy ktoś po prostu go uruchomi, ponieważ ./quote.sh --helpzastanawia się, co robi).

ジ ョ ー ジ
źródło
0

Oto inne rozwiązanie. Ta funkcja pobiera pojedynczy argument i odpowiednio go zacytuje, używając znaku pojedynczego cudzysłowu, tak jak wyjaśniono w głosowaniu powyżej:

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

Możesz więc użyć tego w ten sposób:

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

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
exbuddha
źródło