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

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

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

Responder1

Usandoawk

Se a vírgula estiver sempre no final da penú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]
]

Usandosed

$ 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

Usandobash

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"

Responder2

Simplesmente você pode tentar o comando Perl one-liner abaixo.

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

Explicação:

  • ,Corresponde a uma vírgula.
  • (?!.*,)A antecipação negativa afirma que não haveria uma vírgula após a vírgula correspondente. Portanto, corresponderia à última vírgula.
  • sE o mais importante é so modificador DOTALL, que faz com que o ponto corresponda até mesmo aos caracteres de nova linha.

Responder3

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á aquelas em que 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 ele troca o buffer de linha atual pelo buffer de espera e dessa forma imprime simultaneamente todas as linhas que ocorreram desde a última vírgulaelibera seu buffer de retenção.

Eu estava vasculhando meu arquivo de histórico e encontrei isto:

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

Na verdade, é muito bom. Sim, ele usa eval, mas nunca passa nada além de uma referência numérica aos seus argumentos. Ele cria sedscripts arbitrários para lidar com a ú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 subshell ed da função evalitera todos os seus argumentos uma vez. À medida que passa por eles, ele itera um contador apropriadamente, dependendo do contexto de cada opção, e pula tantos 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 de opções $aadiciona $o. $aé atribuído com base no valor $ique é incrementado pela contagem de argumentos para cada argumento processado. $aé atribuído um dos dois valores a seguir:
    • a=$((i+=1))- isto é atribuído se uma opção curta não tiver seu argumento anexado a ela ou se a opção for longa.
    • a=$i#-?- isto é atribuído se a opção for curta efaztenha seu argumento anexado a ele.
    • a=\${$a}${1:+$d\${$(($1))\}}- Independentemente da atribuição inicial, $ao valor de é sempre colocado entre colchetes e - em um -scaso - às vezes $ié incrementado mais um e o campo delimitado adicional é anexado.

O resultado é que evalnunca é passada uma string contendo incógnitas. Cada um dos argumentos da linha de comando é referido pelo seu número de argumento numérico - até mesmo o delimitador que é extraído do primeiro caractere do primeiro argumento e é o único momento em que você deve usar qualquer caractere sem escape. Basicamente, a função é um gerador de macro - ela nunca interpreta os valores dos argumentos de nenhuma maneira especial porque sedpode(e irá, claro)lidar facilmente com isso ao analisar o script. Em vez disso, ele apenas organiza seus argumentos de maneira sensata em um script viável.

Aqui estão alguns resultados de depuração da função em funcionamento:

... 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 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 do arquivo que segue a última vez /^.0/que foi correspondido, aplica as seguintes substituições:

  • sdd&&&&d- substitui $match-se 4 vezes.
  • sd'dsqd4- a quarta aspa 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 se pode usar lmatchpara remover a última vírgula de um arquivo:

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

SAÍDA:

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

Responder4

verhttps://stackoverflow.com/questions/12390134/remove-comma-from-last-line

Isso funcionou 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 após remover a vírgula, adicionar o caractere] novamente

informação relacionada