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

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

Ich habe eine sehr große CSV-Datei. Wie würden Sie das Allerletzte ,mit sed (oder ähnlich) 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 es pro Datei.

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

Das funktioniert auch nicht

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

Antwort1

Verwenden vonawk

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 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 vonsed

$ 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:

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

Verwenden vonbash

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"

Antwort2

Sie können einfach den folgenden Perl-Einzeilerbefehl ausprobieren.

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

Erläuterung:

  • ,Entspricht einem Komma.
  • (?!.*,)Negativer Lookahead bedeutet, dass nach dem übereinstimmenden Komma kein Komma folgt. Es würde also mit dem letzten Komma übereinstimmen.
  • sUnd das Wichtigste ist sder DOTALL-Modifikator, der dafür sorgt, dass der Punkt auch mit Zeilenumbruchzeichen übereinstimmt.

Antwort3

lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s//\1/;s/^\n//'
}

Dadurch sollte nur das letzte Vorkommen von a ,in jeder Eingabedatei entfernt werden - und es werden trotzdem die Dateien gedruckt, in denen a ,nicht vorkommt. Im Grunde werden damit Zeilenfolgen gepuffert, die kein Komma enthalten.

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

Ich habe gerade in meiner Verlaufsdatei gestöbert 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
)\"";}

Eigentlich ist es ziemlich gut. Ja, es verwendet eval, übergibt ihm aber nie etwas anderes als einen numerischen Verweis auf seine Argumente. Es erstellt beliebige sedSkripte für die Verarbeitung einer letzten Übereinstimmung. Ich zeige es Ihnen:

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 gibt das Folgende auf stderr aus. 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 ed-Subshell der Funktion evaldurchläuft alle ihre Argumente einmal. Während sie diese durchläuft, iteriert sie je nach Kontext für jeden Schalter einen geeigneten Zähler und überspringt so viele Argumente für die nächste Iteration. Von da an führt sie pro Argument eine von mehreren Aktionen aus:

  • Für jede Option fügt der Optionenparser . $ahinzu , basierend auf dem Wert von , der für jedes verarbeitete Argument um den Argumentzähler erhöht wird. wird einer der beiden folgenden Werte zugewiesen: $o$a$i$a
    • a=$((i+=1))- dies wird zugewiesen, wenn entweder einer Kurzoption das zugehörige Argument nicht angehängt ist oder wenn es sich bei der Option um eine Langoption handelte.
    • a=$i#-?- dieser Wert wird vergeben, wenn es sich um eine Short-Option handelt undtutsein Argument daran angehängt haben.
    • a=\${$a}${1:+$d\${$(($1))\}}- Unabhängig von der ursprünglichen Zuweisung $awird der Wert immer in Klammern eingeschlossen und ggf. -snoch $ieinmal erhöht und ein zusätzlich abgegrenztes Feld angehängt.

Das Ergebnis ist, dass evalniemals eine Zeichenfolge mit Unbekannten übergeben wird. Auf jedes der Befehlszeilenargumente wird durch seine numerische Argumentnummer verwiesen - sogar auf das Trennzeichen, das aus dem ersten Zeichen des ersten Arguments extrahiert wird und das einzige Mal ist, dass Sie ein Zeichen verwenden sollten, das nicht maskiert ist. Im Grunde ist die Funktion ein Makrogenerator - sie interpretiert die Werte der Argumente niemals auf besondere Weise, sedda(und wird es natürlich auch)kann das problemlos handhaben, wenn es das Skript analysiert. Stattdessen ordnet es seine Argumente einfach 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
        '

Damit lmatchkönnen Sie ganz einfach Regexes auf Daten anwenden, die der letzten Ü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'

... das unter Berücksichtigung der Teilmenge der Dateieingabe, die auf die letzte /^.0/Übereinstimmung folgt, die folgenden Ersetzungen anwendet:

  • sdd&&&&d- wird $match4-mal durch sich selbst ersetzt.
  • sd'dsqd4- das vierte einfache Anführungszeichen nach dem Zeilenanfang seit der letzten Übereinstimmung.
  • sd"d\dqd2– das Gleiche gilt, aber für Anführungszeichen und global.

lmatchUm zu demonstrieren, wie man das letzte Komma in einer Datei entfernen kann, geht es wie folgt :

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

Antwort4

sehenhttps://stackoverflow.com/questions/12390134/Komma aus der letzten Zeile entfernen

Das hat bei mir funktioniert:

$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.

verwandte Informationen