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

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"

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.

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

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.


2

Se você pode usar tac:

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

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


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]

-1

Tente com o sedcomando abaixo .

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

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нιη
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.