sed - das allerletzte Vorkommen einer Zeichenfolge (ein Komma) in einer Datei entfernen?

15

Ich habe eine sehr große CSV-Datei. Wie würden Sie das allerletzte ,mit sed (oder ähnlichem) entfernen ?

...
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0],
]

Gewünschte Ausgabe

...
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Der folgende sed-Befehl löscht das letzte Vorkommen pro Zeile, aber ich möchte pro Datei.

sed -e 's/,$//' foo.csv

Das funktioniert auch nicht

sed '$s/,//' foo.csv
Spuder
quelle
Befindet sich das Komma immer in der vorletzten Zeile?
John1024
Ja, die vorletzte Zeile
Spuder

Antworten:

12

Verwenden awk

Wenn das Komma immer am Ende der vorletzten Zeile steht:

$ awk 'NR>2{print a;} {a=b; b=$0} END{sub(/,$/, "", a); print a;print b;}'  input
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Verwenden von awkundbash

$ awk -v "line=$(($(wc -l <input)-1))" 'NR==line{sub(/,$/, "")} 1'  input
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Verwenden sed

$ sed 'x;${s/,$//;p;x;};1d'  input
[11911,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11912,0,"BUILDER","2014-10-15","BUILDER",0,0],
[11913,0,"BUILDER","2014-10-15","BUILDER",0,0]
]

Versuchen Sie für OSX und andere BSD-Plattformen Folgendes:

sed -e x -e '$ {s/,$//;p;x;}' -e 1d  input

Verwenden bash

while IFS=  read -r line
do
    [ "$a" ] && printf "%s\n" "$a"
    a=$b
    b=$line
done <input
printf "%s\n" "${a%,}"
printf "%s\n" "$b"
John1024
quelle
Vielleicht liegt es daran, dass ich auf einem Mac bin, aber der Befehl sed gibt einen Fehler aussed: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder 16.10.14
@spuder Ja, OSX hat BSD sedund es ist oft auf subtile Weise anders. Ich habe keinen Zugriff auf OSX, um dies zu testen, aber bitte versuchen Sie essed -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John1024 16.10.14
Ja, dieser zweite hat am Mac
spuder 16.10.14
4

Probieren Sie einfach den folgenden Perl-Einzeilenbefehl aus.

perl -00pe 's/,(?!.*,)//s' file

Erläuterung:

  • , Entspricht einem Komma.
  • (?!.*,)Negative Vorausschau behauptet, dass es nach diesem übereinstimmenden Komma kein Komma mehr geben würde. Es würde also zum letzten Komma passen.
  • sUnd das sWichtigste ist der DOTALL-Modifikator, mit dem Punkt auch für Zeilenumbrüche geeignet ist.
Avinash Raj
quelle
2
Sie könnten auch tun: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Dies funktioniert, weil der erste .*gierig ist, während der zweite nicht ist.
Oleg Vaskevich
4
lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s//\1/;s/^\n//'
}

Das sollte nur das letzte Vorkommen von a ,in einer Eingabedatei entfernen - und es werden weiterhin diejenigen gedruckt, in denen a ,nicht vorkommt. Grundsätzlich werden Zeilenfolgen gepuffert, die kein Komma enthalten.

Wenn es auf ein Komma stößt, tauscht es den aktuellen Zeilenpuffer mit dem Haltepuffer aus und gibt auf diese Weise gleichzeitig alle Zeilen aus, die seit dem letzten Komma aufgetreten sind, und gibt seinen Haltepuffer frei.

Ich habe gerade meine Verlaufsdatei durchsucht und Folgendes gefunden:

lmatch(){ set "USAGE:\
        lmatch /BRE [-(((s|-sub) BRE)|(r|-ref)) REPL [-(f|-flag) FLAG]*]*
"       "${1%"${1#?}"}" "$@"
        eval "${ZSH_VERSION:+emulate sh}"; eval '
        sed "   1x;     \\$3$2!{1!H;\$!d
                };      \\$3$2{x;1!p;\$!d;x
                };      \\$3$2!x;\\$3$2!b'"
        $(      unset h;i=3 p=:-:shfr e='\033[' m=$(($#+1)) f=OPTERR
                [ -t 2 ] && f=$e\2K$e'1;41;17m}\r${h-'$f$e\0m
                f='\${$m?"\"${h-'$f':\t\${$i$e\n}\$1\""}\\c' e=} _o=
                o(){    IFS=\ ;getopts  $p a "$1"       &&
                        [ -n "${a#[?:]}" ]              &&
                        o=${a#-}${OPTARG-${1#-?}}       ||
                        ! eval "o=$f;o=\${o%%*\{$m\}*}"
        };      a(){    case ${a#[!-]}$o in (?|-*) a=;;esac; o=
                        set $* "${3-$2$}{$((i+=!${#a}))${a:+#-?}}"\
                                ${3+$2 "{$((i+=1))$e"} $2
                        IFS=$;  _o=${_o%"${3+$_o} "*}$*\
        };      while   eval "o \"\${$((i+=(OPTIND=1)))}\""
                do      case            ${o#[!$a]}      in
                        (s*|ub)         a s 2 ''        ;;
                        (r*|ef)         a s 2           ;;
                        (f*|lag)        a               ;;
                        (h*|elp)        h= o; break     ;;
                esac;   done;   set -f; printf  "\t%b\n\t" $o $_o
)\"";}

Es ist eigentlich ziemlich gut. Ja, es verwendet eval, aber es übergibt ihm nie etwas anderes als einen numerischen Verweis auf seine Argumente. Es werden beliebige sedSkripte für die Behandlung eines letzten Matches erstellt. Ich werde Ihnen zeigen:

printf "%d\" %d' %d\" %d'\n" $(seq 5 5 200) |                               
    tee /dev/fd/2 |                                                         
    lmatch  d^.0     \  #all re's delimit w/ d now                           
        -r '&&&&'    \  #-r or --ref like: '...s//$ref/...'      
        --sub \' sq  \  #-s or --sub like: '...s/$arg1/$arg2/...'
        --flag 4     \  #-f or --flag appended to last -r or -s
        -s\" \\dq    \  #short opts can be '-s $arg1 $arg2' or '-r$arg1'
        -fg             #tacked on so: '...s/"/dq/g...'                     

Das druckt folgendes zu stderr. Dies ist eine Kopie der lmatchEingabe von:

5" 10' 15" 20'
25" 30' 35" 40'
45" 50' 55" 60'
65" 70' 75" 80'
85" 90' 95" 100'
105" 110' 115" 120'
125" 130' 135" 140'
145" 150' 155" 160'
165" 170' 175" 180'
185" 190' 195" 200'

Die evaled-Subshell der Funktion durchläuft alle Argumente einmal. Wenn es über sie läuft, iteriert es einen Zähler entsprechend dem Kontext für jeden Schalter und überspringt so viele Argumente für die nächste Iteration. Von da an macht es eines von ein paar Dingen pro Argument:

  • Für jede Option fügt die Option Parser $azu $o. $awird basierend auf dem Wert zugewiesen, der $ifür jedes verarbeitete Argument um die Anzahl der Argumente erhöht wird. $aerhält einen der beiden folgenden Werte:
    • a=$((i+=1)) - Dies wird zugewiesen, wenn entweder an eine Short-Option kein Argument angehängt ist oder wenn die Option eine Long-Option war.
    • a=$i#-?- dies wird zugewiesen , wenn die Option ein kurzer und nicht hat seine arg angehängt.
    • a=\${$a}${1:+$d\${$(($1))\}}- Unabhängig von der anfänglichen Zuweisung wird $ader Wert immer in geschweifte Klammern gesetzt und in einigen -sFällen $ium eins erhöht und zusätzlich ein abgegrenztes Feld angehängt.

Das Ergebnis ist, dass evalniemals ein String übergeben wird, der Unbekanntes enthält. Jedes der Befehlszeilenargumente wird durch seine numerische Argumentnummer bezeichnet - selbst das Trennzeichen, das aus dem ersten Zeichen des ersten Arguments extrahiert wird und das einzige Mal ist, dass Sie ein beliebiges Zeichen ohne Maskierung verwenden sollten. Grundsätzlich ist die Funktion ein Makrogenerator - sie interpretiert die Werte der Argumente niemals auf besondere Weise, da seddies beim Parsen des Skripts problemlos möglich (und selbstverständlich auch möglich) ist . Stattdessen ordnet es seine Argumente nur sinnvoll in einem funktionsfähigen Skript an.

Hier ist eine Debug-Ausgabe der Funktion bei der Arbeit:

... sed "   1x;\\$2$1!{1!H;\$!d
        };      \\$2$1{x;1!p;\$!d;x
        };      \\$2$1!x;\\$2$1!b
        s$1$1${4}$1
        s$1${6}$1${7}$1${9}
        s$1${10#-?}$1${11}$1${12#-?}
        "
++ sed '        1x;\d^.0d!{1!H;$!d
        };      \d^.0d{x;1!p;$!d;x
        };      \d^.0d!x;\d^.0d!b
        sdd&&&&d
        sd'\''dsqd4
        sd"d\dqdg
        '

Auf diese Weise lmatchkönnen Sie ganz einfach reguläre Ausdrücke auf Daten anwenden, die auf die letzte Übereinstimmung in einer Datei folgen. Das Ergebnis des Befehls, den ich oben ausgeführt habe, ist:

5" 10' 15" 20'
25" 30' 35" 40'
45" 50' 55" 60'
65" 70' 75" 80'
85" 90' 95" 100'
101010105dq 110' 115dq 120'
125dq 130' 135dq 140sq
145dq 150' 155dq 160'
165dq 170' 175dq 180'
185dq 190' 195dq 200'

... die angesichts der Teilmenge der Dateieingabe, die dem letzten Mal /^.0/folgt, übereinstimmt, die folgenden Ersetzungen anwendet:

  • sdd&&&&d- ersetzt $matchsich 4 mal.
  • sd'dsqd4 - das vierte einfache Anführungszeichen nach dem Zeilenanfang seit dem letzten Spiel.
  • sd"d\dqd2 - ebenso, aber für doppelte Anführungszeichen und global.

So zeigen Sie, wie Sie lmatchdas letzte Komma in einer Datei entfernen können:

printf "%d, %d %d, %d\n" $(seq 5 5 100) |
lmatch '/\(.*\),' -r\\1

AUSGABE:

5, 10 15, 20
25, 30 35, 40
45, 50 55, 60
65, 70 75, 80
85, 90 95 100
mikeserv
quelle
1
@don_crissti - es ist jetzt viel besser - Ich habe die -mOption gelöscht und sie als obligatorisch erklärt, zu mehreren Argumenten für re und repl für gewechselt -sund auch die ordnungsgemäße Behandlung von Begrenzern implementiert. Ich denke, es ist kugelsicher. Ich habe erfolgreich ein Leerzeichen und ein einfaches Anführungszeichen als Trennzeichen verwendet.
mikeserv
2

Wenn das Komma möglicherweise nicht in der vorletzten Zeile steht

Verwenden awkund tac:

tac foo.csv | awk '/,$/ && !handled { sub(/,$/, ""); handled++ } {print}' | tac

Der awkBefehl ist einfach, um die Substitution durchzuführen, wenn das Muster zum ersten Mal angezeigt wird.  tacKehrt die Reihenfolge der Zeilen in der Datei um, sodass der awkBefehl das letzte Komma entfernt.

Mir wurde das gesagt

tac foo.csv | awk '/,$/ && !handled { sub(/,$/, ""); handled++ } {print}' > tmp && tac tmp

kann effizienter sein.

G-Man sagt, "Monica wiedereinsetzen"
quelle
2

Wenn Sie verwenden können tac:

tac file | perl -pe '$_=reverse;!$done && s/,// && $done++;$_=reverse'|tac
Joseph R.
quelle
1

Siehe /programming/12390134/remove-comma-from-last-line

Das hat bei mir geklappt:

$cat input.txt
{"name": "secondary_ua","type":"STRING"},
{"name": "request_ip","type":"STRING"},
{"name": "cb","type":"STRING"},
$ sed '$s/,$//' < input.txt >output.txt
$cat output.txt
{"name": "secondary_ua","type":"STRING"},
{"name": "request_ip","type":"STRING"},
{"name": "cb","type":"STRING"}

Am besten entferne ich die letzte Zeile und füge nach dem Entfernen des Kommas das Zeichen] erneut hinzu

Yu Jiaao
quelle
1

Versuchen Sie es mit unten vi:

  vi "+:$-1s/\(,\)\(\_s*]\)/\2/e" "+:x" file

Erläuterung:

  • $-1 Wählen Sie die vorletzte Zeile

  • s ersetzen

  • \(,\)\(\_s*]\)Suchen Sie ein Komma, gefolgt von ]und getrennt durch Leerzeichen oder Zeilenumbrüche
  • \2Ersetzen durch \(\_s*]\)zB Leerzeichen oder Zeilenvorschub gefolgt von]
knisterstern
quelle
-1

Versuchen Sie es mit dem folgenden sedBefehl.

sed -i '$s/,$//' foo.csv
Sachin
quelle
1
Dies entfernt das Komma aus jeder Zeile. Dies ist nicht der Fall, wenn OP gewünscht wird.
Archemar
@Archemar Nein, es wird nur in der letzten Zeile entfernt, aber das funktioniert nicht für OPs Daten, die nicht in der letzten Zeile stehen
α --sнιη