Liczba ukośników zwrotnych potrzebnych do ucieczki ukośnika odwrotnego w wierszu polecenia

12

Niedawno miałem problem z pewnym wyrażeniem regularnym w wierszu poleceń i odkryłem, że do dopasowania odwrotnego ukośnika można użyć różnej liczby znaków. Liczba ta zależy od cytowania użytego w wyrażeniu regularnym (brak, pojedyncze cudzysłowy, podwójne cudzysłowy). Zobacz, co mam na myśli, w poniższej sesji bash:

echo "#ab\\cd" > file
grep -E ab\cd file
grep -E ab\\cd file
grep -E ab\\\cd file
grep -E ab\\\\cd file
#ab\cd
grep -E ab\\\\\cd file
#ab\cd
grep -E ab\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\\cd file
grep -E "ab\cd" file
grep -E "ab\\cd" file
grep -E "ab\\\cd" file
#ab\cd
grep -E "ab\\\\cd" file
#ab\cd
grep -E "ab\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\\cd" file
grep -E 'ab\cd' file
grep -E 'ab\\cd' file
#ab\cd
grep -E 'ab\\\cd' file
#ab\cd
grep -E 'ab\\\\cd' file

To znaczy że:

  • bez cudzysłowów, mogę dopasować odwrotny ukośnik z 4-7 rzeczywistymi odwrotnymi ukośnikami
  • z podwójnymi cudzysłowami mogę dopasować odwrotny ukośnik z 3-6 rzeczywistymi odwrotnymi ukośnikami
  • Z pojedynczymi cudzysłowami mogę dopasować odwrotny ukośnik z 2-3 rzeczywistymi odwrotnymi ukośnikami

Rozumiem, że jeden dodatkowy ukośnik odwrotny jest ignorowany przez powłokę (ze strony podręcznika bash):

„Niecytowany ukośnik odwrotny (\) to znak zmiany znaczenia. Zachowuje on dosłowną wartość następnego następującego po nim znaku”

Nie dotyczy to przykładów z pojedynczymi cudzysłowami, ponieważ w pojedynczych cudzysłowach nie jest wykonywana żadna zmiana znaczenia.

Jeden dodatkowy ukośnik odwrotny jest ignorowany przez polecenie grep („\ c” to tylko „c”, ale jest to to samo co „c”, ponieważ „c” nie ma specjalnego znaczenia w wyrażeniu regularnym).

Wyjaśnia to zachowanie przykładu z pojedynczymi cudzysłowami, ale tak naprawdę nie rozumiem dwóch pozostałych przykładów, szczególnie dlaczego istnieje różnica między nieokreślonymi ciągami cudzysłowów.

Ponownie cytat ze strony podręcznika użytkownika bash:

„Umieszczanie znaków w podwójnych cudzysłowach zachowuje dosłowną wartość wszystkich znaków w cudzysłowach, z wyjątkiem $,`, \ oraz, gdy włączone jest rozszerzanie historii,! ”.

Próbowałem tego samego z GNU awk (np. awk /ab\cd/{print} file), Z tymi samymi wynikami.

Perl pokazuje jednak różne wyniki (używając np. perl -ne "/ab\\cd/"\&\&print file):

  • bez cudzysłowów, mogę dopasować odwrotny ukośnik z 4-5 rzeczywistymi odwrotnymi ukośnikami
  • z podwójnymi cudzysłowami mogę dopasować odwrotny ukośnik z 3-4 rzeczywistymi odwrotnymi ukośnikami
  • Z pojedynczymi cudzysłowami mogę dopasować odwrotny ukośnik z 2 rzeczywistymi odwrotnymi ukośnikami

Czy ktoś może wyjaśnić tę różnicę między ciągami wyrażeń regularnych niekwotowanych i podwójnie znakowanych w wierszu poleceń dla grep i awk? Wyjaśnienie zachowania Perla nie interesuje mnie tak bardzo, ponieważ zazwyczaj nie korzystam z linijek Perla.

Daniel Kullmann
źródło

Odpowiedzi:

10

W nie cytowanym przykładzie każda \\para przekazuje jeden odwrotny ukośnik do grep, więc 4 odwrotne ukośniki przekazują dwa do grep, co przekłada się na pojedynczy odwrotny ukośnik. 6 odwrotnych ukośników przekazuje trzy do grep, co przekłada się na jeden odwrotny ukośnik i jeden \c, który jest równy c. Jeden dodatkowy ukośnik nic nie zmienia, ponieważ jest tłumaczony \c-> cprzez powłokę. Osiem odwrotnych ukośników w powłoce to cztery w grep, przetłumaczone na dwa, więc to już nie pasuje.

Na przykład w podwójnych cudzysłowach zwróć uwagę na drugi cytat ze strony podręcznika bash:

Ukośnik odwrotny zachowuje swoje specjalne znaczenie tylko wtedy, gdy następuje po nim jeden z następujących znaków: $, `,", \ lub nowa linia.

Tzn. Gdy podajesz nieparzystą liczbę odwrotnych ukośników, sekwencja kończy się na \c, co byłoby równe cw przypadku niecytowanym, ale gdy jest cytowany, odwrotny ukośnik traci swoje specjalne znaczenie, więc \cjest przekazywany do grep. Dlatego zakres „możliwych” ukośników odwrotnych (tj. Tworzących wzorzec pasujący do pliku przykładowego) przesuwa się o jeden.

Ansgar Esztermann
źródło
... a potem są pewne osobliwości: na przykład: printf "\ntest"wstawi nowy wiersz przed „testem”, nawet jeśli "\n"powinien być przetłumaczony "n"przez powłokę, ponieważ zawiera podwójne cudzysłowy ... (więc oczekiwany wynik powinien być, dla "\ ntest", "ntest". Powinniśmy mieć nawyk pisania: printf "\\ntest"lub printf '\ntest', ale jakoś widzę dużo skryptu polegającego na osobliwości.
Olivier Dulac
6

Ten link opisuje Bash Quotes and Escaping

Twoje pytanie dotyczy trzech pierwszych części.

  • Ucieczka na postać
  • Słabe cytowanie „podwójnych cudzysłowów”
  • Mocne cytowanie „pojedynczych cytatów”
  • ANSI C jak ciąg znaków
  • Cytowanie I18N / L10N (internacjonalizacja i lokalizacja) .

Poniżej znajduje się wykres, w jaki sposób ciągi bashte są przekazywane grepi jak grepdalej interpretuje je wewnętrznie.

Spójrzmy na echo "#ab\\cd" > file.
W słabej cytowane ( „”) "#ab\\cd"The \\jest uciec \, które wprowadza się do filepostaci pojedynczego dosłownym \. Więc filezawiera ab\cd

Teraz do twoich poleceń: Poniższa tabela może pomóc zobaczyć, co faktycznie dzieje się z każdym połączeniem. *Pokazuje te, które odpowiadają zawartości plików. Tak naprawdę to tylko kwestia zastosowania reguł ucieczki basha, tak jak na stronie internetowej, ze szczególnym uwzględnieniem odpowiedzi Daniela Kullmanna, w której odnosi się do zachowania ucieczki w sytuacji słabego cytowania .

Ukośnik odwrotny zachowuje swoje specjalne znaczenie tylko wtedy, gdy następuje po nim jeden z następujących znaków: $, `,", \ lub nowa linia.


                            bash passes    grep further
                            to grep        resolves to         
grep -E ab\cd file            abcd           abcd   
grep -E ab\\cd file           ab\cd          abcd  
grep -E ab\\\cd file          ab\cd          abcd
grep -E ab\\\\cd file         ab\\cd         ab\cd    * 
grep -E ab\\\\\cd file        ab\\\cd        ab\cd    *
grep -E ab\\\\\\cd file       ab\\\cd        ab\cd    *    
grep -E ab\\\\\\\cd file      ab\\\cd        ab\cd    *
grep -E ab\\\\\\\\cd file     ab\\\\cd       ab\\cd

grep -E "ab\cd" file          ab\cd          abcd
grep -E "ab\\cd" file         ab\cd          abcd
grep -E "ab\\\cd" file        ab\\cd         ab\cd    *
grep -E "ab\\\\cd" file       ab\\cd         ab\cd    *
grep -E "ab\\\\\cd" file      ab\\\cd        ab\cd    *
grep -E "ab\\\\\\cd" file     ab\\\cd        ab\cd    *
grep -E "ab\\\\\\\cd" file    ab\\\\cd       ab\\cd    

grep -E 'ab\cd' file          ab\cd          abcd  
grep -E 'ab\\cd' file         ab\\cd         ab\cd    *
grep -E 'ab\\\cd' file        ab\\\cd        ab\cd    *
grep -E 'ab\\\\cd' file       ab\\\\cd       ab\\cd
Peter.O
źródło