Anzahl der erforderlichen Backslashes, um Regex-Backslashes in der Befehlszeile zu maskieren

12

Ich hatte kürzlich Probleme mit einigen regulären Ausdrücken in der Befehlszeile und stellte fest, dass für die Suche nach einem umgekehrten Schrägstrich unterschiedliche Anzahlen von Zeichen verwendet werden können. Diese Zahl hängt von der für den regulären Ausdruck verwendeten Anführungszeichen ab (keine, einfache Anführungszeichen, doppelte Anführungszeichen). In der folgenden Bash-Sitzung erfahren Sie, was ich meine:

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

Das bedeutet, dass:

  • Ohne Anführungszeichen kann ich einen Backslash mit 4-7 tatsächlichen Backslashes abgleichen
  • mit doppelten Anführungszeichen kann ich einen Backslash mit 3-6 tatsächlichen Backslashes abgleichen
  • Mit einfachen Anführungszeichen kann ich einen Backslash mit 2-3 tatsächlichen Backslashes abgleichen

Ich verstehe, dass ein zusätzlicher Backslash von der Shell ignoriert wird (aus der Bash-Manpage):

"Ein Backslash (\) ohne Anführungszeichen ist das Escape-Zeichen. Es behält den Literalwert des nächsten folgenden Zeichens bei."

Dies gilt nicht für Beispiele mit einfachen Anführungszeichen, da in einfachen Anführungszeichen kein Escapezeichen verwendet wird.

Ein zusätzlicher Backslash wird vom grep-Befehl ignoriert ("\ c" wird nur mit "c" maskiert, dies ist jedoch genau das Gleiche wie "c", da "c" in einer Regex keine besondere Bedeutung hat).

Dies erklärt das Verhalten des Beispiels mit einfachen Anführungszeichen, aber ich verstehe die beiden anderen Beispiele nicht wirklich, insbesondere, warum es einen Unterschied zwischen nicht in Anführungszeichen gesetzten und nicht in Anführungszeichen gesetzten Zeichenfolgen gibt.

Wieder ein Zitat aus der Bash-Manpage:

Msgstr "Das Einschließen von Zeichen in doppelte Anführungszeichen behält den Literalwert aller Zeichen in den Anführungszeichen bei, mit Ausnahme von $,`, \ und, wenn die Verlaufserweiterung aktiviert ist,!. "

Ich habe dasselbe mit GNU awk (zB awk /ab\cd/{print} file) versucht, mit den gleichen Ergebnissen.

Perl zeigt jedoch unterschiedliche Ergebnisse (unter Verwendung von zB perl -ne "/ab\\cd/"\&\&print file):

  • Ohne Anführungszeichen kann ich einen Backslash mit 4-5 tatsächlichen Backslashes abgleichen
  • mit doppelten Anführungszeichen kann ich einen Backslash mit 3-4 tatsächlichen Backslashes abgleichen
  • Mit einfachen Anführungszeichen kann ich einen Backslash mit 2 tatsächlichen Backslashs abgleichen

Kann jemand den Unterschied zwischen nicht zitierten und doppelten regulären Zeichenfolgen in der Befehlszeile für grep und awk erklären? Ich bin nicht sonderlich an einer Erklärung von Perls Verhalten interessiert, da ich normalerweise keine Perl-Einzeiler verwende.

Daniel Kullmann
quelle

Antworten:

10

Für das nicht zitierte Beispiel \\übergibt jedes Paar einen Backslash an grep, sodass 4 Backslashs zwei an grep übergeben, was zu einem einzelnen Backslash führt. 6 Backslashes ergeben drei zu grep, was einen Backslash und einen Backslash \cergibt, der gleich ist c. Ein zusätzlicher Backslash ändert nichts, weil es übersetzt wird \c-> cdurch die Schale. Acht Backslashes in der Shell sind vier in grep, übersetzt in zwei, sodass dies nicht mehr passt.

Beachten Sie für das Beispiel in doppelten Anführungszeichen, was auf Ihr zweites Zitat in der bash-Manpage folgt:

Der Backslash behält seine spezielle Bedeutung nur bei, wenn auf ihn eines der folgenden Zeichen folgt: $, `,", \ oder Newline.

Das heißt, wenn Sie eine ungerade Anzahl von Backslashes angeben, endet die Sequenz mit \c, was cim nicht zitierten Fall gleich wäre. Wenn der Backslash jedoch in Anführungszeichen gesetzt wird, verliert er seine spezielle Bedeutung und \cwird an grep übergeben. Aus diesem Grund verschiebt sich der Bereich der "möglichen" Backslashes (dh derjenigen, die ein Muster ergeben, das zu Ihrer Beispieldatei passt) um eins.

Ansgar Esztermann
quelle
... und dann gibt es einige Kuriositäten: zum Beispiel: printf "\ntest"fügt vor "test" eine neue Zeile ein, obwohl "\n"sie "n"von der Shell in doppelte Anführungszeichen übersetzt werden sollte ... (so sollte das erwartete Ergebnis z "\ ntest", "ntest". Wir sollten uns angewöhnen, zu schreiben: printf "\\ntest"oder printf '\ntest', aber irgendwie sehe ich viel Skript, das stattdessen auf der Kuriosität beruht.
Olivier Dulac
6

Dieser Link beschreibt Bash Quotes und Escaping

Ihre Frage befasst sich mit den ersten drei Abschnitten.

  • Flucht pro Zeichen
  • Schwache Anführungszeichen
  • Starke Anführungszeichen
  • ANSI C wie String Quoting
  • I18N / L10N-Quotierung (Internationalisierung und Lokalisierung) .

Unten sehen Sie eine Tabelle, wie die Zeichenfolgen bashsie weiterleiten grepund wie grepsie intern weiter interpretiert werden.

Schauen wir uns zuerst an echo "#ab\\cd" > file.
Im schwach zitierten ("") "#ab\\cd"ist das \\ein Escapezeichen, \das fileals einzelnes Literal übergeben wird \. Also, fileenthält ab\cd

Nun zu Ihren Befehlen: Die folgende Tabelle kann Ihnen helfen, zu sehen, was bei jedem Anruf tatsächlich passiert. Das *zeigt diejenigen, die dem Dateiinhalt entsprechen. Es geht eigentlich nur darum, die Escape-Regeln von bash anzuwenden, wie auf der Webseite, wobei insbesondere auf die Antwort von daniel kullmann hingewiesen wird , in der er sich auf das Escape- Verhalten in einer Situation mit schwachen Zitaten bezieht .

Der Backslash behält seine spezielle Bedeutung nur bei, wenn auf ihn eines der folgenden Zeichen folgt: $, `,", \ oder Newline.


                            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
quelle