wie man die ersten beiden Spalten in einer Datei mit der Shell entfernt (awk, sed, was auch immer)

73

Ich habe eine Datei mit vielen Zeilen in jeder Zeile. Es gibt viele Spalten (Felder), die durch Leerzeichen getrennt sind. Die Anzahl der Spalten in jeder Zeile ist unterschiedlich. Ich möchte die ersten beiden Spalten entfernen. Wie geht das?

wenzi
quelle
Mögliches Duplikat von Verwenden von awk zum Drucken aller Spalten vom n-ten bis zum letzten
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

Antworten:

148

Sie können es tun mit cut:

cut -d " " -f 3- input_filename > output_filename

Erläuterung:

  • cut: Rufen Sie den Befehl cut auf
  • -d " ": Verwenden Sie ein einzelnes Leerzeichen als Trennzeichen ( cutverwendet standardmäßig TAB)
  • -f: Felder angeben, die beibehalten werden sollen
  • 3-: Alle Felder beginnend mit Feld 3
  • input_filename: Verwenden Sie diese Datei als Eingabe
  • > output_filename: Schreibe die Ausgabe in diese Datei.

Alternativ können Sie dies tun mit awk:

awk '{$1=""; $2=""; sub("  ", " "); print}' input_filename > output_filename

Erläuterung:

  • awk: Rufen Sie den Befehl awk auf
  • $1=""; $2="";: Setzen Sie Feld 1 und 2 auf die leere Zeichenfolge
  • sub(...);: Bereinigen Sie die Ausgabefelder, da die Felder 1 und 2 weiterhin durch "" begrenzt werden.
  • print: Drucke die geänderte Zeile
  • input_filename > output_filename: das gleiche wie oben.
Sampson-Chen
quelle
@wenzi oops, vergessen, dass cutstandardmäßig tab als Trennzeichen verwendet. Siehe aktualisierte Antwort - gerade getestet und es funktioniert. Wenn alles andere gleich ist, würde ich empfehlen, cutover zu verwenden awk.
Sampson-Chen
Sie könnten es in awk mit nur tun awk '{sub(/([^ ]+ ){2}/, "")}1'. Ich bin damit einverstanden, dass Schnitt sowieso die bessere Wahl ist, wenn Sie ein Einzelzeichen-Feldtrennzeichen haben.
Ed Morton
Es sind noch einige Leerzeichen übrig, verwenden Sie awk '{$1=""; $2=""; sub(/^ +/, ""); print}'stattdessen oder kürzerawk '{$1=$2=""; sub(/^ +/, "")}1'
Jirislav
26

Hier ist eine Möglichkeit, dies mit Awk zu tun, die relativ einfach zu verstehen ist:

awk '{print substr($0, index($0, $3))}'

Dies ist ein einfacher awk-Befehl ohne Muster, sodass {}für jede Eingabezeile eine Aktion ausgeführt wird.

Die Aktion besteht darin, den Teilstring einfach beginnend mit der Position des 3. Felds zu drucken.

  • $0: die gesamte Eingabezeile
  • $3: 3. Feld
  • index(in, find): Gibt die Position von findin string zurückin
  • substr(string, start): Rückgabe eines Teilstrings ab Index start

Wenn Sie ein anderes Trennzeichen wie Komma verwenden möchten, können Sie es mit der Option -F angeben:

awk -F"," '{print substr($0, index($0, $3))}'

Sie können dies auch für eine Teilmenge der Eingabezeilen ausführen, indem Sie vor der Aktion in ein Muster angeben {}. Nur Linien, die dem Muster entsprechen, werden ausgeführt.

awk 'pattern{print substr($0, index($0, $3))}'

Wo Muster etwas sein kann wie:

  • /abcdef/: Verwenden Sie einen regulären Ausdruck, arbeitet standardmäßig mit $ 0.
  • $1 ~ /abcdef/: auf einem bestimmten Feld arbeiten.
  • $1 == blabla: String-Vergleich verwenden
  • NR > 1: Datensatz- / Zeilennummer verwenden
  • NF > 0: Feld- / Spaltennummer verwenden
Raychi
quelle
1
Vielen Dank dafür, es ist eine schönere Antwort als die akzeptierte IMO
Alex Forbes
Wie wäre es, wenn Sie die letzten 2 Spalten entfernen und von hinten zählen?
CMCDragonkai
10
Dies funktioniert nicht richtig, wenn Feld 2 und Feld 3 denselben Inhalt haben.
PHP Learner
12

Vielen Dank für die Veröffentlichung der Frage. Ich möchte auch das Skript hinzufügen, das mir geholfen hat.

awk '{ $1=""; print $0 }' file
Felipe Alvarez
quelle
1
Awk behält in diesem Fall keine Feldtrennzeichen bei.
Timurb
Sie können hinzufügen OFS=FS, um die Trennzeichen beizubehalten
MichaelChirico
9
awk '{$1=$2="";$0=$0;$1=$1}1'

Eingang

a b c d

Ausgabe

c d
Steven Penny
quelle
Kannst du bitte Erklären? :) Ich verstehe nicht, warum es notwendig ist, $0=$0;$1=$1dass die Leerzeichen verschwinden
jirislav
6

Sie können verwenden sed:

sed 's/^[^ ][^ ]* [^ ][^ ]* //'

Dies sucht nach Zeilen, die mit einem oder mehreren Nicht-Leerzeichen, einem Leerzeichen, einem weiteren Satz von einem oder mehreren Nicht-Leerzeichen und einem weiteren Leerzeichen beginnen, und löscht das übereinstimmende Material, auch bekannt als die ersten beiden Felder. Das [^ ][^ ]*ist geringfügig kürzer als die entsprechende, aber explizitere [^ ]\{1,\}Notation, und das zweite kann auf Probleme mit GNU stoßen sed(wenn Sie es --posixals Option verwenden, sedkann es sogar GNU nicht vermasseln). OTOH, wenn die zu wiederholende Zeichenklasse komplexer war, gewinnt die nummerierte Notation der Kürze halber. Es ist einfach, dies zu erweitern, um "Leerzeichen oder Tabulatoren" als Trennzeichen oder "mehrere Leerzeichen" oder "mehrere Leerzeichen oder Registerkarten" zu behandeln. Es kann auch geändert werden, um optionale führende Leerzeichen (oder Tabulatoren) vor dem ersten Feld usw. zu behandeln.

Für awkund cutfinden Sampson-Chen ‚s Antwort . Es gibt andere Möglichkeiten, das awkSkript zu schreiben , aber sie sind nicht wesentlich besser als die gegebene Antwort. Beachten Sie, dass Sie das Feldtrennzeichen möglicherweise explizit ( -F" ") festlegen müssen, awkwenn Sie nicht möchten, dass Registerkarten als Trennzeichen behandelt werden, oder wenn zwischen den Feldern mehrere Leerzeichen stehen. Der POSIX-Standard cutunterstützt nicht mehrere Trennzeichen zwischen Feldern. GNU cutverfügt über die nützliche, aber nicht standardmäßige -iOption, um mehrere Trennzeichen zwischen Feldern zuzulassen.

Sie können es auch in reiner Schale tun:

while read junk1 junk2 residue
do echo "$residue"
done < in-file > out-file
Jonathan Leffler
quelle
Wenn residueein Backslash enthalten kann, wird dieser durch den obigen Lesevorgang interpretiert und nicht in der Ausgabe reproduziert. Immer benutzen while IFS= read -r ....
Ed Morton
Wenn bashder Inhalt mit einer Ebene interpretiert wird read, bashist er (erneut) fehlerhaft. Der Lesebefehl in Original-Shells hat keinen solchen Unsinn gemacht. Ich glaube nicht, dass es von der POSIX-Shell benötigt wird. Es würde mich irritieren, wenn ich finde, dass bashes das tut, was Sie sagen - ich habe bereits eine Hassliebe zu dem Programm, da es viele Dinge gut macht, aber es gibt einige Dinge, die es schlecht macht und sich ändert Legacy-Verhalten ist eines der schlimmsten, und die Anforderung einer Option zur Aktivierung des alten Standardverhaltens ist ... sehr irritierend. Es scheint, du hast recht; bashist gegabelt!
Jonathan Leffler
Dieses Verhalten ist POSIX, siehe pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html .
Ed Morton
Ich habe es nicht explizit gesagt, aber der Grund, warum Sie IFS = benötigen, ist, dass wenn das erste Feld in der Eingabe leer wäre, die Standardfeldaufteilung führende Leerzeichen entfernen würde und daher residuebei Feld 4 (oder höher) anstelle von Feld 3 beginnen würde .
Ed Morton
Verdammt. Ok; POSIX ist kaputt, bashfolgt aber POSIX 2008. Ich wollte diese Funktionalität in mehr als einem Vierteljahrhundert der Shell-Programmierung nie, aber ich denke, ich muss in einer Minderheit sein.
Jonathan Leffler
6

Es ist ziemlich einfach, es nur mit Shell zu machen

while read A B C; do
echo "$C"
done < oldfile >newfile
Technosaurus
quelle
Dies ist eine großartige Antwort, die Sie jedoch read -ranstelle von verwenden möchten read.
Robert
read -rbewahrt Backslashes. readwird nicht. Zum Beispiel: echo "foo ba\r"erzeugt eine Ausgabe von foo ba\r. Allerdings echo "foo ba\r" | (while read first_column second_column; do echo "$second_column"; done)produziert nur barals der Ausgang (mit dem umgekehrten Schrägstrich entfernt , um das Hinzufügen von . -rFlagge der korrekte Ausgabe von produziertba\r
Robert
4

Perl:

perl -lane 'print join(' ',@F[2..$#F])' File

awk:

awk '{$1=$2=""}1' File
Vijay
quelle
1

Dies könnte für Sie funktionieren (GNU sed):

sed -r 's/^([^ ]+ ){2}//' file

oder für Spalten, die durch einen oder mehrere Leerzeichen getrennt sind:

sed -r 's/^(\S+\s+){2}//' file
Potong
quelle
1

Wenn Sie awk verwenden und auf einigen der folgenden Optionen basieren, wird die Verwendung einer for-Schleife etwas flexibler. Manchmal möchte ich vielleicht die ersten 9 Spalten löschen (wenn ich zum Beispiel ein "ls -lrt" mache), also ändere ich die 2 gegen eine 9 und das war's:

awk '{ for(i=0;i++<2;){$i=""}; print $0 }' your_file.txt

Carlos
quelle
0

Verwenden Sie kscript

kscript 'lines.split().select(-1,-2).print()' file
Holger Brandl
quelle