sed - remove a última ocorrência de uma string (vírgula) em um arquivo?

15

Eu tenho um arquivo csv muito grande. Como você removeria o último ,com sed (ou 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],
]

Saída desejada

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

O comando sed a seguir excluirá a última ocorrência por linha, mas eu quero por arquivo.

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

Isso também não funciona

sed '$s/,//' foo.csv
spuder
fonte
A vírgula está sempre na penúltima linha?
John1024
Sim, a penúltima linha
spuder 15/10

Respostas:

12

Usando awk

Se a vírgula estiver sempre no final da segunda à última linha:

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

Usando 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 e outras plataformas BSD, tente:

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

Usando 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
fonte
Talvez seja porque eu estou em um mac, mas o comando sed dá errosed: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder
@ spuder Sim, o OSX tem BSD sede geralmente é diferente de maneiras sutis. Eu não tenho acesso a OSX para testar isso, mas por favor tentesed -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John1024
Sim, isso segundo um trabalhou no Mac
spuder
4

Simplesmente, você pode tentar o comando Perl de uma linha abaixo.

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

Explicação:

  • , Corresponde a uma vírgula.
  • (?!.*,)Lookahead negativo afirma que não haveria uma vírgula depois dessa vírgula correspondente. Portanto, corresponderia à última vírgula.
  • sE o mais importante é o smodificador DOTALL, que também faz com que o ponto corresponda até aos caracteres de nova linha.
Avinash Raj
fonte
2
Você também pode fazer: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Isso funciona porque o primeiro .*é ganancioso, enquanto o segundo não é.
Oleg Vaskevich
4
lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s//\1/;s/^\n//'
}

Isso deve remover apenas a última ocorrência de a ,em qualquer arquivo de entrada - e ainda imprimirá aqueles nos quais a ,não ocorre. Basicamente, ele armazena em buffer sequências de linhas que não contêm vírgula.

Quando encontra uma vírgula, troca o buffer de linha atual pelo buffer de retenção e, dessa maneira, imprime simultaneamente todas as linhas que ocorreram desde a última vírgula e libera seu buffer de retenção.

Eu estava apenas pesquisando meu arquivo de histórico e encontrei o seguinte:

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

É realmente muito bom. Sim, ele usa eval, mas nunca lhe passa nada além de uma referência numérica a seus argumentos. Ele cria sedscripts arbitrários para lidar com uma última correspondência. Eu vou te mostrar:

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

Isso imprime o seguinte em stderr. Esta é uma cópia da lmatchentrada 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'

O evalsubshell ed da função repete todos os argumentos uma vez. À medida que caminha sobre eles, itera um contador adequadamente, dependendo do contexto de cada opção e ignora muitos argumentos para a próxima iteração. A partir de então, ele faz uma de algumas coisas por argumento:

  • Para cada opção o analisador opção adiciona $aa $o. $aé atribuído com base no valor $iincrementado pela contagem de argumentos para cada argumento processado. $aé atribuído um dos dois seguintes valores:
    • a=$((i+=1)) - é atribuído se uma opção curta não tem seu argumento anexado ou se a opção era longa.
    • a=$i#-?- este é atribuído se a opção é curta e não têm a sua arg anexado a ele.
    • a=\${$a}${1:+$d\${$(($1))\}}- Independentemente da atribuição inicial, $ao valor de sempre é colocado entre chaves e - em um -scaso - algumas vezes $ié incrementado mais um campo adicional e delimitado é anexado.

O resultado é que evalnunca é passada uma string que contém incógnitas. Cada um dos argumentos da linha de comando é referido por seu número numérico - mesmo o delimitador que é extraído do primeiro caractere do primeiro argumento e é a única vez em que você deve usar qualquer caractere que não tiver escapado. Basicamente, a função é um gerador de macro - nunca interpreta os valores dos argumentos de nenhuma maneira especial, porque sedpode (e será, é claro) facilmente manipular isso quando analisa o script. Em vez disso, apenas organiza sensivelmente seus argumentos em um script viável.

Aqui estão algumas saídas de depuração da função no trabalho:

... 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 assim lmatchpode ser usado para aplicar facilmente expressões regulares aos dados após a última correspondência em um arquivo. O resultado do comando que eu executei acima é:

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 o subconjunto da entrada de arquivo que se segue à última vez em que /^.0/é correspondido, aplica as seguintes substituições:

  • sdd&&&&d- substitui $match-se 4 vezes.
  • sd'dsqd4 - a quarta aspas simples após o início da linha desde a última partida.
  • sd"d\dqd2 - Idem, mas para aspas duplas e globalmente.

E assim, para demonstrar como alguém pode usar lmatchpara remover a última vírgula de um arquivo:

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

RESULTADO:

5, 10 15, 20
25, 30 35, 40
45, 50 55, 60
65, 70 75, 80
85, 90 95 100
mikeserv
fonte
1
@don_crissti - está muito melhor agora - larguei a -mopção e a tornei obrigatória, mudei para vários argumentos para re e repl -se também implementei o manuseio adequado do delimitador. Eu acho que é à prova de balas. I utilizado com sucesso tanto um espaço e uma única citação como delimitador,
mikeserv
2

Se a vírgula não estiver na penúltima linha

Usando awke tac:

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

O awkcomando é simples de fazer a substituição na primeira vez que o padrão é visto.  tacinverte a ordem das linhas no arquivo, portanto, o awkcomando acaba removendo a última vírgula.

Me disseram isso

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

pode ser mais eficiente.

G-Man Diz 'Reinstate Monica'
fonte
2

Se você pode usar tac:

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

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

Isso é trabalhado para mim:

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

Minha melhor maneira é remover a última linha e depois de remover a vírgula, adicione o] char novamente

Yu Jiaao
fonte
1

Tente com abaixo vi:

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

Explicação:

  • $-1 selecione a penúltima linha

  • s substituir

  • \(,\)\(\_s*]\)encontre uma vírgula seguida por ]e separada por espaços ou nova linha
  • \2substitua por \(\_s*]\)espaços ou nova linha seguidos por]
knisterstern
fonte
-1

Tente com o sedcomando abaixo .

sed -i '$s/,$//' foo.csv
Sachin
fonte
1
Isso removerá a vírgula de rastreamento de todas as linhas, não é o que o OP deseja.
Archemar 8/08/19
@Archemar Não, ele será removido apenas na última linha, mas isso não funcionará para os dados do OP que não estão na última linha
αғsнιη