sed: ¿eliminar la última aparición de una cadena (una coma) en un archivo?

15

Tengo un archivo csv muy grande. ¿Cómo eliminarías el último ,con sed (o similar)?

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

Salida deseada

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

El siguiente comando sed eliminará la última aparición por línea, pero quiero por archivo.

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

Tampoco funciona

sed '$s/,//' foo.csv
Spuder
fuente
¿La coma siempre está en la penúltima línea?
John1024
Sí, la segunda a la última línea
spuder

Respuestas:

12

Utilizando awk

Si la coma siempre está al final de la penúltima línea:

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

Usando awkybash

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

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

Para OSX y otras plataformas BSD, intente:

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

Utilizando 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
fuente
Tal vez sea porque estoy en un Mac, pero el comando sed da errorsed: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder
@spuder Sí, OSX tiene BSD sedy a menudo es diferente en formas sutiles. No tengo acceso a OSX para probar esto, pero por favor intentesed -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John1024
Sí, ese segundo trabajó en Mac
spuder
4

Simplemente puede probar el siguiente comando Perl one-liner.

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

Explicación:

  • , Coincide con una coma.
  • (?!.*,)La anticipación negativa afirma que no habría una coma después de esa coma coincidente. Entonces coincidiría con la última coma.
  • sY lo más importante es el smodificador DOTALL que hace que el punto coincida incluso con los caracteres de nueva línea.
Avinash Raj
fuente
2
También puede hacer: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Esto funciona porque el primero .*es codicioso, mientras que el segundo no lo es.
Oleg Vaskevich
4
lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s//\1/;s/^\n//'
}

Eso debería eliminar solo la última aparición de a ,en cualquier archivo de entrada, y aún imprimirá aquellos en los que ,no ocurra. Básicamente, almacena secuencias de líneas que no contienen una coma.

Cuando encuentra una coma, intercambia el búfer de línea actual con el búfer de retención y de esa manera imprime simultáneamente todas las líneas que ocurrieron desde la última coma y libera su búfer de retención.

Estaba buscando en mi archivo de historial y encontré esto:

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

En realidad es bastante bueno. Sí, lo usa eval, pero nunca le pasa nada más allá de una referencia numérica a sus argumentos. Crea sedscripts arbitrarios para manejar una última coincidencia. Te mostrare:

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

Eso imprime lo siguiente para stderr. Esta es una copia de lmatchla entrada de:

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'

El evalsubshell de la función ed recorre todos sus argumentos una vez. A medida que avanza sobre ellos, itera un contador de manera apropiada según el contexto de cada cambio y omite esa cantidad de argumentos para la próxima iteración. A partir de entonces, hace una de las pocas cosas por argumento:

  • Para cada opción la opción analizador agrega $aa $o. $ase asigna en función del valor del $icual se incrementa por conteo de arg para cada arg procesado. $ase le asigna uno de los dos valores siguientes:
    • a=$((i+=1)) - esto se asigna si una opción corta no tiene su argumento adjunto o si la opción era larga.
    • a=$i#-?- esto se asigna si la opción es corta y no tener su arg anexa a la misma.
    • a=\${$a}${1:+$d\${$(($1))\}}- Independientemente de la asignación inicial, $ael valor de siempre se incluye entre llaves y, en un -scaso, a veces $ise incrementa uno más y se agrega un campo delimitado adicionalmente.

El resultado es que evalnunca se pasa una cadena que contenga incógnitas. Se hace referencia a cada uno de los argumentos de la línea de comandos por su número de argumento numérico, incluso el delimitador que se extrae del primer carácter del primer argumento y es la única vez que debe usar cualquier carácter que no esté escapado. Básicamente, la función es un generador de macros: nunca interpreta los valores de los argumentos de ninguna manera especial porque sedpuede (y lo hará, por supuesto) manejarlo fácilmente cuando analiza el script. En cambio, simplemente organiza sus argumentos en un guión viable.

Aquí hay algunos resultados de depuración de la función en el trabajo:

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

Y así lmatchse puede usar para aplicar fácilmente expresiones regulares a los datos después de la última coincidencia en un archivo. El resultado del comando que ejecuté arriba es:

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'

... que, dado el subconjunto de la entrada del archivo que sigue a la última vez /^.0/que coincide, aplica las siguientes sustituciones:

  • sdd&&&&d- reemplaza $matchcon 4 veces sí.
  • sd'dsqd4 - la cuarta comilla simple que sigue al comienzo de la línea desde el último partido.
  • sd"d\dqd2 - lo mismo, pero para comillas dobles y globalmente.

Y así, para demostrar cómo se puede usar lmatchpara eliminar la última coma en un archivo:

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

SALIDA:

5, 10 15, 20
25, 30 35, 40
45, 50 55, 60
65, 70 75, 80
85, 90 95 100
mikeserv
fuente
1
@don_crissti - ahora está mucho mejor - dejé la -mopción y la hice obligatoria, cambié a múltiples argumentos para re y repl -sy también implementé el manejo adecuado del delimitador. Creo que es a prueba de balas. Utilicé con éxito un espacio y una comilla simple como delimitador,
mikeserv
2

Si la coma puede no estar en la penúltima línea

El uso awky tac:

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

El awkcomando es simple para hacer la sustitución la primera vez que se ve el patrón.  tacinvierte el orden de las líneas en el archivo, por lo que el awkcomando termina eliminando el último coma.

Me han dicho que

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

Puede ser más eficiente.

G-Man dice 'restablecer a Mónica'
fuente
2

Si puedes usar tac:

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

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

Esto me funcionó:

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

Mi mejor manera es eliminar la última línea y después de eliminar la coma, agregue el] char nuevamente

Yu Jiaao
fuente
1

Prueba con a continuación vi:

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

Explicación:

  • $-1 seleccione penúltima línea

  • s reemplazar

  • \(,\)\(\_s*]\)encuentra una coma seguida de ]y separada por espacios o nueva línea
  • \2reemplazar por \(\_s*]\)ej. espacios o nueva línea seguidos por]
Knisterstern
fuente
-1

Prueba con el siguiente sedcomando.

sed -i '$s/,$//' foo.csv
Sachin
fuente
1
Esto eliminará la coma de seguimiento de cada línea, esto no es lo que OP desea.
Archemar
@Archemar No, se eliminará solo en la última línea, pero eso no funcionará para los datos de OP que no están en la última línea
aғsнιη