sed - menghapus kemunculan string (koma) terakhir dalam file?


15

Saya memiliki file csv yang sangat besar. Bagaimana Anda menghapus yang terakhir ,dengan sed (atau serupa)?

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

Output yang diinginkan

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

Perintah sed berikut akan menghapus kejadian terakhir per baris, tetapi saya ingin per file.

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

Ini juga tidak bekerja

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

Apakah koma selalu ada di baris kedua hingga terakhir?
John1024

Ya, baris kedua ke baris terakhir
spuder

Jawaban:


12

Menggunakan awk

Jika koma selalu di akhir baris kedua hingga terakhir:

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

Menggunakan awkdanbash

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

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

Untuk OSX dan platform BSD lainnya, cobalah:

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

Menggunakan 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"

Mungkin karena saya di mac, tetapi perintah sed memberikan kesalahansed: 1: "x;${s/,$//;p;x}; 2,$ p": extra characters at the end of x command
spuder

@spuder Ya, OSX memiliki BSD seddan seringkali berbeda dengan cara yang halus. Saya tidak memiliki akses ke OSX untuk menguji ini, tapi tolong cobased -n -e x -e '${s/,$//;p;x;}' -e '2,$ p' input
John1024

Ya, yang kedua bekerja di Mac
spuder

4

Cukup Anda bisa mencoba perintah Perl one-liner di bawah ini.

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

Penjelasan:

  • , Cocok dengan koma.
  • (?!.*,)Lookahead negatif menyatakan bahwa tidak akan ada koma setelah koma yang cocok. Jadi itu akan cocok dengan koma terakhir.
  • sDan hal yang paling penting adalah spengubah DOTALL yang membuat titik untuk mencocokkan bahkan karakter baris baru juga.

2
Anda juga bisa melakukan: perl -0777 -pi -e 's/(.*),(.*?)/\1\2/s'. Ini berhasil karena yang pertama .*serakah, sedangkan yang kedua tidak.
Oleg Vaskevich

4
lcomma() { sed '
    $x;$G;/\(.*\),/!H;//!{$!d
};  $!x;$s//\1/;s/^\n//'
}

Itu harus menghapus hanya kejadian terakhir ,dalam file input apa pun - dan itu masih akan mencetak yang ,tidak terjadi. Pada dasarnya, ini mendukung urutan garis yang tidak mengandung koma.

Ketika bertemu koma, ia menukar buffer garis saat ini dengan buffer terus dan dengan cara itu secara bersamaan mencetak semua baris yang terjadi sejak koma terakhir dan membebaskan buffer penahannya.

Saya baru saja menggali file sejarah saya dan menemukan ini:

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

Sebenarnya cukup bagus. Ya, itu digunakan eval, tetapi tidak pernah melewati apa pun di luar referensi numerik ke argumennya. Itu membangun sedskrip sewenang-wenang untuk menangani pertandingan terakhir. Saya akan menunjukkan kepada Anda:

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

Yang mencetak berikut ini ke stderr. Ini adalah salinan lmatchinput:

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'

evalSubshell fungsi ed iterates melalui semua argumennya sekali. Ketika ia berjalan di atasnya mereka itu counter yang tepat tergantung pada konteks untuk setiap switch dan melompati banyak argumen untuk iterasi berikutnya. Sejak saat itu ia melakukan satu dari beberapa hal per argumen:

  • Untuk setiap opsi pilihan parser menambahkan $ake $o. $aditugaskan berdasarkan nilai $iyang bertambah dengan jumlah arg untuk setiap arg yang diproses. $aditugaskan salah satu dari dua nilai berikut:
    • a=$((i+=1)) - ini diberikan jika salah satu opsi pendek tidak memiliki argumennya ditambahkan atau jika opsi itu panjang.
    • a=$i#-?- ini ditugaskan jika opsi adalah pendek dan tidak memiliki arg yang ditambahkan untuk itu.
    • a=\${$a}${1:+$d\${$(($1))\}}- Terlepas dari penugasan awal, $anilai selalu dibungkus dalam kurung dan - dalam -skasus - kadang-kadang $ibertambah satu lagi dan bidang tambahan dibatasi ditambahkan.

Hasilnya adalah bahwa evaltidak pernah melewati string yang mengandung sesuatu yang tidak diketahui. Setiap argumen baris perintah dirujuk dengan nomor argumen numeriknya - bahkan pembatas yang diekstraksi dari karakter pertama argumen pertama dan merupakan satu-satunya waktu Anda harus menggunakan karakter apa pun yang tidak terhindar. Pada dasarnya, fungsinya adalah generator makro - ia tidak pernah menginterpretasikan nilai argumen dengan cara khusus karena seddapat (dan akan, tentu saja) dengan mudah mengatasinya ketika mem-parsing skrip. Alih-alih, itu hanya dengan bijaksana mengatur argumennya menjadi naskah yang bisa diterapkan.

Berikut ini beberapa hasil debug fungsi di tempat kerja:

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

Dan lmatchdapat digunakan untuk dengan mudah menerapkan regex ke data setelah pertandingan terakhir dalam sebuah file. Hasil dari perintah yang saya jalankan di atas adalah:

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'

... yang, mengingat subset dari input file yang mengikuti terakhir kali /^.0/dicocokkan, menerapkan substitusi berikut:

  • sdd&&&&d- Mengganti $matchsendiri 4 kali.
  • sd'dsqd4 - kutipan tunggal keempat mengikuti awal baris sejak pertandingan terakhir.
  • sd"d\dqd2 - Dita, tetapi untuk tanda kutip ganda dan global.

Jadi, untuk mendemonstrasikan bagaimana seseorang dapat menggunakan lmatchuntuk menghapus koma terakhir dalam file:

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

KELUARAN:

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

1
@don_crissti - ini jauh lebih baik sekarang - saya menjatuhkan -mpilihan dan membuatnya wajib, beralih ke beberapa argumen untuk kembali dan membalas -sdan juga menerapkan penanganan pembatas yang tepat. Saya pikir ini anti peluru. Saya berhasil menggunakan spasi dan kutipan tunggal sebagai pembatas,
mikeserv

2

Jika koma mungkin tidak ada di baris kedua hingga terakhir

Menggunakan awkdan tac:

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

The awkperintah yang sederhana untuk melakukan substitusi pertama kalinya pola terlihat.  tacmembalik urutan baris dalam file, sehingga awkperintah akhirnya menghapus koma terakhir .

Saya sudah diberitahu itu

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

mungkin lebih efisien.


2

Jika Anda dapat menggunakan tac:

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

1

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

Ini bekerja untuk saya:

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

Cara terbaik saya adalah menghapus baris terakhir dan setelah menghapus koma, tambahkan char] lagi


1

Coba dengan di bawah ini vi:

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

Penjelasan:

  • $-1 pilih baris kedua hingga terakhir

  • s menggantikan

  • \(,\)\(\_s*]\)temukan koma yang diikuti oleh ]dan dipisahkan oleh spasi atau baris baru
  • \2ganti dengan \(\_s*]\)spasi atau baris baru diikuti oleh]

-1

Coba dengan sedperintah di bawah ini .

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

1
Ini akan menghapus trailling koma dari setiap baris, ini bukan apa yang diinginkan OP.
Archemar

@Archemar Tidak, ini hanya akan dihapus pada baris terakhir tetapi itu tidak akan berfungsi untuk data OP yang tidak ada di baris terakhir
αғsнιη
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.