Warum erkennt sed \ t nicht als Tab?

105
sed "s/\(.*\)/\t\1/" $filename > $sedTmpFile && mv $sedTmpFile $filename

Ich erwarte, dass dieses sedSkript tabvor jeder Zeile ein $filenameeinfügt, dies ist jedoch nicht der Fall. Aus irgendeinem Grund wird tstattdessen ein eingefügt.

sechzig Fuß
quelle
1
Da sed zwischen den Plattformen variieren kann (insbesondere BSD / MacOSX im Vergleich zu Linux), kann es hilfreich sein, die Plattform anzugeben, auf der Sie sed verwenden.
Isaac
sed "s / (. *) / # \ 1 /" $ filename | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ Dateiname.
user2432405
Informationen zu OS X-Benutzern (macOS) finden Sie in dieser Frage .
Franklin Yu

Antworten:

129

Nicht alle Versionen von sedverstehen \t. Fügen Sie stattdessen einfach eine wörtliche Registerkarte ein (drücken Sie Ctrl- Vdann Tab).

Mark Byers
quelle
2
Ah ja; zur Verdeutlichung: Nicht alle Versionen von sed verstehen \tim Ersatzteil des Ausdrucks (es wird \tim Mustervergleichsteil gut erkannt )
John Weldon
3
awwwwwwwwwwwwwwwwwww, ok das ist ziemlich interessant. Und seltsam. Warum sollten Sie es an einem Ort erkennen lassen, aber nicht am anderen ...?
Sixtyfootersdude
2
Von einem Skript aufgerufen, funktioniert das nicht: Tabs werden von sh ignoriert. Mit dem folgenden Code aus einem Shell-Skript wird beispielsweise $ TEXT_TO_ADD hinzugefügt, ohne dass eine Tabelle vorangestellt wird: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson
2
@Dereckson und andere - siehe diese Antwort: stackoverflow.com/a/2623007/48082
Cheeso
2
Dereckson s / kann / kann /?
Douglas Held
41

Mit Bash können Sie ein TAB-Zeichen programmgesteuert wie folgt einfügen:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
sedit
quelle
Das ist sehr hilfreich.
Cheeso
1
Sie waren auf dem richtigen Weg mit der $'string'aber fehlenden Erklärung. Tatsächlich vermute ich aufgrund der äußerst umständlichen Verwendung, dass Sie wahrscheinlich ein unvollständiges Verständnis haben (wie die meisten von uns mit Bash). Siehe meine Erklärung unten: stackoverflow.com/a/43190120/117471
Bruno Bronosky
1
Denken Sie daran, dass BASH Variablen nicht wie $TABin einfachen Anführungszeichen erweitert, daher müssen Sie doppelte Anführungszeichen verwenden.
Nealmcb
Vorsicht bei der Verwendung von *doppelten Anführungszeichen ... dies wird als Glob behandelt, nicht als Regex, den Sie beabsichtigen.
Levigroker
27

@sedit war auf dem richtigen Weg, aber es ist etwas umständlich, eine Variable zu definieren.

Lösung (Bash-spezifisch)

Die Möglichkeit, dies in Bash zu tun, besteht darin, ein Dollarzeichen vor Ihre einfache Anführungszeichenfolge zu setzen.

$ echo -e '1\n2\n3'
1
2
3

$ echo -e '1\n2\n3' | sed 's/.*/\t&/g'
t1
t2
t3

$ echo -e '1\n2\n3' | sed $'s/.*/\t&/g'
    1
    2
    3

Wenn Ihre Zeichenfolge eine Variablenerweiterung enthalten muss, können Sie Zeichenfolgen in Anführungszeichen wie folgt zusammenstellen:

$ timestamp=$(date +%s)
$ echo -e '1\n2\n3' | sed "s/.*/$timestamp"$'\t&/g'
1491237958  1
1491237958  2
1491237958  3

Erläuterung

In Bash $'string'verursacht "ANSI-C-Erweiterung". Und das ist es, was die meisten von uns erwarten , wenn wir Dinge verwenden , wie \t, \r, \nusw. Von: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Wörter der Form $ 'string' werden speziell behandelt. Das Wort wird zu Zeichenfolge erweitert , wobei Zeichen mit Backslash-Escapezeichen gemäß ANSI C-Standard ersetzt werden. Backslash-Escape-Sequenzen werden, falls vorhanden, dekodiert ...

Das erweiterte Ergebnis wird in einfachen Anführungszeichen gesetzt, als ob das Dollarzeichen nicht vorhanden gewesen wäre.

Lösung (wenn Sie Bash vermeiden müssen)

Ich persönlich halte die meisten Bemühungen, Bash zu vermeiden, für dumm, weil das Vermeiden von Bashismen Ihren Code NICHT portabel macht. (Ihr Code wird weniger spröde sein, wenn Sie ihn verprügeln, bash -euals wenn Sie versuchen, Bash zu vermeiden und zu verwenden sh[es sei denn, Sie sind ein absoluter POSIX-Ninja].) Aber anstatt ein religiöses Argument darüber zu haben, gebe ich Ihnen nur das BESTE * Antworten.

$ echo -e '1\n2\n3' | sed "s/.*/$(printf '\t')&/g"
    1
    2
    3

* Beste Antwort? Ja, denn ein Beispiel dafür, was die meisten Anti-Bash-Shell-Scripter in ihrem Code falsch machen würden, ist die Verwendung echo '\t'wie in der Antwort von @ robrecord . Das funktioniert für GNU-Echo, aber nicht für BSD-Echo. Dies wird von The Open Group unter http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 erklärt . Dies ist ein Beispiel dafür, warum der Versuch, Bashismen zu vermeiden, normalerweise fehlschlägt.

Bruno Bronosky
quelle
8

Ich habe so etwas mit einer Bash-Shell unter Ubuntu 12.04 (LTS) verwendet:

So fügen Sie eine neue Zeile mit der Registerkarte hinzu: die zweite, wenn die erste übereinstimmt:

sed -i '/first/a \\t second' filename

So ersetzen Sie zuerst mit Registerkarte zweiten :

sed -i 's/first/\\t second/g' filename
Thomas Bratt
quelle
4
Die doppelte Flucht ist der Schlüssel, dh verwenden \\tund nicht \t.
Zamnuts
Ich musste auch doppelte Anführungszeichen anstelle von einfachen Anführungszeichen unter Ubuntu 16.04 und Bash 4.3 verwenden.
Caw
4

Verwenden $(echo '\t') . Sie benötigen Anführungszeichen um das Muster.

Z.B. So entfernen Sie eine Registerkarte:

sed "s/$(echo '\t')//"
Robrecord
quelle
5
Es ist lustig, dass Sie eine "GNU-Echo" -spezifische Funktion verwenden (\ t als Tabulatorzeichen interpretieren), um einen "BSD sed" -spezifischen Fehler zu beheben (\ t als 2 separate Zeichen interpretieren). Wenn Sie "GNU-Echo" haben, hätten Sie vermutlich auch "GNU sed". In diesem Fall müssten Sie kein Echo verwenden. Mit BSD gibt echo echo '\t'2 separate Zeichen aus. Die tragbare Methode von POSIX ist die Verwendung printf '\t'. Deshalb sage ich: Versuchen Sie nicht, Ihren Code portabel zu machen, indem Sie keine Bash verwenden. Es ist schwieriger als du denkst. Verwenden bashist das tragbarste, was die meisten von uns tun können.
Bruno Bronosky
3

Sie müssen keine sedErsetzung durchführen, wenn Sie tatsächlich nur eine Registerkarte vor der Zeile einfügen möchten. Das Ersetzen dieses Falls ist im Vergleich zum Ausdrucken ein teurer Vorgang, insbesondere wenn Sie mit großen Dateien arbeiten. Es ist auch einfacher zu lesen, da es kein Regex ist.

zB mit awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename
Ghostdog74
quelle
0

sedunterstützt nicht \t, noch andere Escape-Sequenzen wie \nfür diese Angelegenheit. Die einzige Möglichkeit, die ich gefunden habe, bestand darin, das Tabulatorzeichen mithilfe von in das Skript einzufügensed .

Möglicherweise möchten Sie jedoch Perl oder Python verwenden. Hier ist ein kurzes Python-Skript, das ich geschrieben habe und das ich für alle Stream-Regexing verwende:

#!/usr/bin/env python
import sys
import re

def main(args):
  if len(args) < 2:
    print >> sys.stderr, 'Usage: <search-pattern> <replace-expr>'
    raise SystemExit

  p = re.compile(args[0], re.MULTILINE | re.DOTALL)
  s = sys.stdin.read()
  print p.sub(args[1], s),

if __name__ == '__main__':
  main(sys.argv[1:])
Roman Nurik
quelle
2
Und die Perl-Version wäre der Shell-Einzeiler "perl-pes / a / b / 'Dateiname" oder "etwas | perl-pes / a / b /'"
tiftik
0

Anstelle von BSD sed verwende ich Perl:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
   hi
Cees Timmerman
quelle
0

Ich denke, andere haben dies für andere Ansätze angemessen geklärt ( sed,AWK usw.). Es bashfolgen jedoch meine spezifischen Antworten (getestet unter macOS High Sierra und CentOS 6/7).

1) Wenn OP eine Such- und Ersetzungsmethode verwenden möchte, die der ursprünglich vorgeschlagenen ähnelt, würde ich vorschlagen, perldiese wie folgt zu verwenden. Hinweise: Backslashes vor Klammern für Regex sollten nicht erforderlich sein. Diese Codezeile zeigt, wie sie $1besser verwendet werden kann als \1mit dem perlSubstitutionsoperator (z. B. gemäß Perl 5-Dokumentation ).

perl -pe 's/(.*)/\t$1/' $filename > $sedTmpFile && mv $sedTmpFile $filename

2) Wie jedoch von ghostdog74 hervorgehoben , würde ich , da die gewünschte Operation darin besteht, einfach eine Registerkarte am Anfang jeder Zeile hinzuzufügen , bevor die tmp-Datei in die Eingabe- / Zieldatei ( $filename) geändert wird , perlerneut empfehlen , jedoch mit der folgenden Änderung (s):

perl -pe 's/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename
## OR
perl -pe $'s/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename

3) Natürlich ist die tmp-Datei überflüssig , daher ist es besser, einfach alles an Ort und Stelle zu tun ( -iFlag hinzufügen ) und die Dinge zu einem eleganteren Einzeiler zu vereinfachen

perl -i -pe $'s/^/\t/' $filename
justincbagley
quelle