sed - rimuove l'ultima occorrenza di una stringa (una virgola) in un file?


15

Ho un file CSV molto grande. Come rimuoveresti l'ultimo ,con sed (o simile)?

...
[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],
]

Uscita desiderata

...
[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]
]

Il seguente comando sed cancellerà l'ultima occorrenza per riga, ma voglio per file.

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

Né funziona

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

La virgola è sempre sulla penultima riga?
Giovanni 1024

Sì, la penultima riga
spuder

Risposte:


12

utilizzando awk

Se la virgola è sempre alla fine della penultima riga:

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

Utilizzando awkebash

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

utilizzando 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]
]

Per OSX e altre piattaforme BSD, prova:

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

utilizzando 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"

Forse è perché sono su un mac, ma il comando sed dà erroresed: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder

@spuder Sì, OSX ha BSD seded è spesso diverso in modo sottile. Non ho accesso a OSX per testare questo, ma per favore provased -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John 1024

Sì, quel secondo ha funzionato su Mac
spuder il

4

Potresti semplicemente provare il seguente comando Perl one-liner.

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

Spiegazione:

  • , Corrisponde a una virgola.
  • (?!.*,)Lookahead negativo afferma che non ci sarebbe una virgola dopo quella virgola abbinata. Quindi corrisponderebbe all'ultima virgola.
  • sE la cosa più importante è il smodificatore DOTALL che rende il punto adatto anche ai caratteri di nuova riga.

2
Si potrebbe anche fare: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Questo funziona perché il primo .*è goloso, mentre il secondo no.
Oleg Vaskevich,

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

Ciò dovrebbe rimuovere solo l'ultima occorrenza di a ,in qualsiasi file di input e stamperà comunque quelle in cui a ,non si verifica. Fondamentalmente, buffer sequenze di righe che non contengono una virgola.

Quando incontra una virgola, scambia il buffer di riga corrente con il buffer di mantenimento e in questo modo stampa simultaneamente tutte le righe che si sono verificate dall'ultima virgola e libera il buffer di conservazione.

Stavo solo scavando nel mio file di cronologia e ho trovato questo:

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
)\"";}

In realtà è abbastanza buono. Sì, lo usa eval, ma non gli passa mai nulla oltre un riferimento numerico ai suoi argomenti. Costruisce sedscript arbitrari per gestire un'ultima partita. Ti mostrerò:

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

Che stampa quanto segue su stderr. Questa è una copia lmatchdell'input:

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'

La evalsubshell ed della funzione scorre una volta tutti gli argomenti. Mentre cammina su di essi, itera un contatore in modo appropriato a seconda del contesto di ogni switch e salta su molti argomenti per la successiva iterazione. Da allora in poi fa una delle poche cose per argomento:

  • Per ogni opzione aggiunta $aal parser di opzioni $o. $aè assegnato in base al valore di $icui è incrementato dal conteggio arg per ogni arg elaborato. $aviene assegnato uno dei due seguenti valori:
    • a=$((i+=1)) - questo viene assegnato se a un'opzione corta non è stato aggiunto l'argomento o se l'opzione era lunga.
    • a=$i#-?- questo viene assegnato se l'opzione è breve e non avere il suo arg allegati.
    • a=\${$a}${1:+$d\${$(($1))\}}- Indipendentemente dall'assegnazione iniziale, $ail valore viene sempre racchiuso tra parentesi graffe e - in un -scaso - a volte $iviene incrementato di un altro e viene aggiunto un campo delimitato ulteriormente.

Il risultato è che evalnon viene mai passata una stringa contenente eventuali incognite. A ciascuno degli argomenti della riga di comando viene fatto riferimento il loro numero di argomento numerico, anche il delimitatore che viene estratto dal primo carattere del primo argomento ed è l'unica volta in cui dovresti usare qualunque carattere senza caratteri di escape. Fondamentalmente, la funzione è un generatore di macro: non interpreta mai i valori degli argomenti in un modo speciale perché sedpuò (e ovviamente,) gestirlo facilmente quando analizza lo script. Invece, organizza in modo ragionevole i suoi argomenti in una sceneggiatura praticabile.

Ecco alcuni output di debug della funzione al lavoro:

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

E così lmatchpuò essere usato per applicare facilmente regex ai dati che seguono l'ultima corrispondenza in un file. Il risultato del comando che ho eseguito sopra è:

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'

... che, dato il sottoinsieme del file di input che segue l'ultima volta che /^.0/corrisponde, applica le seguenti sostituzioni:

  • sdd&&&&d- Sostituisce $matchcon se stesso 4 volte.
  • sd'dsqd4 - la quarta virgoletta singola che segue l'inizio della riga dall'ultima partita.
  • sd"d\dqd2 - idem, ma per virgolette doppie e globalmente.

E così, per dimostrare come si potrebbe usare lmatchper rimuovere l'ultima virgola in un file:

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

PRODUZIONE:

5, 10 15, 20
25, 30 35, 40
45, 50 55, 60
65, 70 75, 80
85, 90 95 100

1
@don_crissti - ora è molto meglio - ho lasciato cadere l' -mopzione e l'ho resa obbligatoria, sono passata a più argomenti per la re e la sostituzione -se ho anche implementato la corretta gestione del delimitatore. Penso che sia a prova di proiettile. Ho usato con successo sia uno spazio che una singola citazione come delimitatore,
mikeserv,

2

Se la virgola potrebbe non essere sulla penultima riga

Utilizzando awke tac:

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

Il awkcomando è semplice per effettuare la sostituzione la prima volta che si vede lo schema.  tacinverte l'ordine delle linee nel file, quindi il awkcomando finisce per rimuovere il ultima virgola.

Mi è stato detto questo

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

potrebbe essere più efficiente.


2

Se puoi usare tac:

tac file | perl -pe '$_=reverse;!$done && s/,// && $done++;$_=reverse'|tac

1

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

Questo ha funzionato per me:

$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"}

Il mio modo migliore è rimuovere l'ultima riga e dopo aver rimosso la virgola, aggiungere di nuovo il carattere]


1

Prova con sotto vi:

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

Spiegazione:

  • $-1 seleziona dalla penultima riga

  • s sostituire

  • \(,\)\(\_s*]\)trova una virgola seguita da ]e separata da spazi o newline
  • \2sostituisci con \(\_s*]\)spazi o newline seguiti da]

-1

Prova con il sedcomando seguente .

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

1
Questo rimuoverà la virgola finale da ogni riga, non è ciò che OP desidera.
Archemar,

@Archemar No, rimuoverà solo sull'ultima riga ma non funzionerà per i dati di OP che non sono nell'ultima riga
α Junsнιη
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.