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

14

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 seguinte comando sed excluirá a última ocorrência por linha, mas eu quero por arquivo.

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

Nem isso funciona

sed '$s/,//' foo.csv
    
por spuder 16.10.2014 / 01:35

8 respostas

11

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 awk e bash

$ 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 o 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"
    
por 16.10.2014 / 01:47
4

Você pode simplesmente experimentar o comando abaixo do Perl one-liner.

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

Explicação:

  • , Corresponde a uma vírgula.
  • (?!.*,) Negativo lookahead afirma que não haveria uma vírgula após a vírgula correspondente. Então, isso corresponderia à última vírgula.
  • s E a coisa mais importante é o modificador s DOTALL, que faz com que o ponto coincida com os caracteres da nova linha também.
por 19.10.2014 / 18:31
4
lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s///;s/^\n//'
}

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

Quando ele encontra uma vírgula, ele troca o buffer de linha atual pelo buffer de retenção e, dessa forma, 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 achei isso:

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='3[' m=$(($#+1)) f=OPTERR
                [ -t 2 ] && f=$eK$e'1;41;17m}\r${h-'$f$e
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...'                     
m f='\${$m?"\"${h-'$f':\t\${$i$e\n}\\""}\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 passa nada além de uma referência numérica aos seus argumentos. Ele cria scripts sed arbitrários para manipular uma última correspondência. Eu vou te mostrar:

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'

Isso imprime o seguinte para stderr. Esta é uma cópia da entrada de lmatch :

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

A subcamada eval ed da função itera todos os seus argumentos uma vez. À medida que ele passa por cima deles, itera um contador apropriadamente, dependendo do contexto de cada opção, e ignora esses muitos argumentos para a próxima iteração. A partir de então, faz uma das poucas coisas por argumento:

  • Para cada opção, o analisador de opções adiciona $a a $o . $a é atribuído com base no valor de $i , que é incrementado pela contagem de argumentos para cada argumento processado. $a recebe um dos dois valores a seguir:
    • a=$((i+=1)) - isso é atribuído se uma opção curta não tiver seu argumento anexado a ela ou se a opção for longa.
    • a=$i#-? - isso é atribuído se a opção é curta e faz ter seu argumento anexado a ela.
    • a=\${$a}${1:+$d\${$(($1))\}} - Independentemente da atribuição inicial, o valor de $a é sempre agrupado em chaves e - em -s caso - às vezes $i é incrementado mais uma vez e o campo adicionalmente delimitado é acrescentado.

O resultado é que eval nunca é passado uma string contendo quaisquer incógnitas. Cada um dos argumentos da linha de comando é referenciado por seu número numérico de argumento - até mesmo o delimitador que é extraído do primeiro caractere do primeiro argumento e é a única vez em que você deve usar qualquer caractere que esteja sem escape. Basicamente, a função é um gerador de macro - ele nunca interpreta os valores dos argumentos de maneira especial porque sed pode (em, claro) lidar facilmente com isso quando analisa o script. Em vez disso, apenas organiza sensatamente seus argumentos em um script viável.

Aqui está uma saída de depuração da função no trabalho:

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'

E assim lmatch pode ser usado para aplicar regexes facilmente aos dados após a última correspondência em um arquivo. O resultado do comando que eu corri acima é:

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

... que, dado o subconjunto da entrada do arquivo que segue a última vez que /^.0/ é correspondido, aplica as seguintes substituições:

  • sdd&&&&d - substitui $match por si mesmo 4 vezes.
  • sd'dsqd4 - a quarta aspa simples após o início da linha desde a última correspondência.
  • sd"d\dqd2 - idem, mas para aspas duplas e globalmente.

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

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

OUTPUT:

lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s///;s/^\n//'
}
    
por 16.10.2014 / 09:07
2

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

Usando awk e tac :

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

O comando awk é simples para fazer a substituição a primeira vez que o padrão é visto. tac inverte a ordem das linhas no arquivo, então o comando awk acaba removendo a última vírgula .

Já me disseram que

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

pode ser mais eficiente.

    
por 16.10.2014 / 02:24
2

Se você puder usar tac :

tac file | perl -pe '$_=reverse;!$done && s/,// && $done++;$_=reverse'|tac
    
por 19.10.2014 / 21:22
0

veja link

Isso funciona 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, adicionar o char novamente

    
por 24.11.2014 / 13:58
0

Tente abaixo de vi :

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

Explicação:

  • $-1 seleciona o segundo para a última linha

  • s substituir

  • \(,\)\(\_s*]\) encontra uma vírgula seguida por ] e é separada por espaços ou nova linha
  • substituir por \(\_s*]\) ou seja, espaços ou nova linha seguidos por ]
por 03.01.2018 / 11:46
-1

Tente com o comando sed abaixo.

sed -i '$s/,$//' foo.csv
    
por 08.08.2017 / 15:11

Tags