Cara menghapus garis jika mengandung karakter tepat sekali

10

Saya ingin menghapus baris dari file yang hanya berisi karakter tertentu, jika ada lebih dari satu kali atau tidak ada maka simpan baris dalam file.

Sebagai contoh:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

Di sini, karakter yang ingin saya hapus Cbegitu, perintahnya harus menghapus garis FGTHDCdan JUTDYCkarena mereka memiliki Ctepat sekali.

Bagaimana saya bisa melakukan ini menggunakan salah satu sedatau awk?

Namz
sumber

Jawaban:

20

Di awkAnda dapat mengatur pemisah bidang untuk apa pun. Jika Anda mengaturnya C, maka Anda akan memiliki banyak bidang +1 sebanyak kejadian C.

Jadi, jika Anda mengatakan awk -F'C' '{print NF}' <<< "C1C2C3"Anda mendapat 4: CCCterdiri dari 3 Cdetik, dan karenanya 4 bidang.

Anda ingin menghapus garis yang Cterjadi tepat sekali. Mempertimbangkan ini, dalam kasus Anda, Anda ingin menghapus garis-garis di mana ada dua Cbidang. Jadi lewati saja:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD
fedorqui
sumber
4
Penggunaan awkpemisah bidang yang cerdik!
Valentin B.
interresting, seperti dalam kasus default (FS = "") ia mengabaikan spasi terdepan ($ 1 = non-spasi pertama pada baris) dan juga pengulangan (Anda dapat memiliki 5 spasi untuk memisahkan bidang 1 dan bidang 2) ... spasi mungkin dirawat secara khusus? (untuk melihatnya, seseorang dapat melakukan awk 'BEGIN { print "FS={" FS"}","OFS={" OFS "}";} {printf "%d fields : ",NF; for (i=1;i<=NF;i++) {printf "{" $i "} ";}; print "" }'dan memberinya makan beberapa baris, beberapa memiliki banyak spces, dan yang lainnya mulai dengan spasi)
Olivier Dulac
2
@OlivierDulac, ya, ruang ditangani secara khusus seperti yang ditentukan oleh POSIX .
Wildcard
8

pendekatan sed :

sed -i '/^[^C]*C[^C]*$/d' input

-i Opsi memungkinkan modifikasi file di tempat

/^[^C]*C[^C]*$/- cocok dengan garis yang Chanya mengandung sekali

d - hapus baris yang cocok

RomanPerekhrest
sumber
8

Ini dapat dilakukan dengan sed:

Kode:

sed '/C.*C/p;/C/d' file1

Hasil:

DTHGTY
HYTRHD
HTCCYD

Bagaimana?

  1. Cocokkan dan cetak baris apa pun dengan setidaknya dua salinan Cvia/C.*C/p
  2. Hapus baris apa pun dengan Cvia /C/d, ini termasuk baris yang sudah dicetak pada langkah 1
  3. Default mencetak sisa garis
Stephen Rauch
sumber
2
Pendekatan alternatif yang cerdas; Saya suka itu.
Wildcard
6

Ini menghilangkan garis dengan tepat satu kemunculan C.

grep -v '^[^C]*C[^C]*$' file

Ekspresi reguler [^C]cocok dengan satu karakter yang bukan C (atau baris baru), dan operator pengulangan (alias bintang Kleene) *menentukan nol atau lebih pengulangan dari ekspresi sebelumnya.

Output default dari grep(dan sebagian besar alat berorientasi teks lainnya) adalah output standar; redirect ke file baru dan mungkin memindahkannya di atas file asli jika itu yang Anda inginkan. Regex yang sama dapat digunakan dengan sed -iuntuk mengedit di tempat:

sed -i '/^[^C]*C[^C]*$/d' file

(Pada beberapa platform, terutama * BSD termasuk macOS, -iopsi membutuhkan argumen, seperti -i ''.)

tripleee
sumber
1
sed -i '/^[^C]*C[^C]*$/d' file- Kedengarannya seperti itu diposting sebelumnya, bagaimana menurut Anda, plagiarisme?
RomanPerekhrest
1
Memang ada duplikasi. Saya mulai dengan grepjawabannya tetapi jelas dengan mudah meluas ke sed -ivarian. Tidak melihat jawaban Anda karena saya mencari grepjawaban sebelumnya .
tripleee
1
Lebih aman untuk hanya menghindari -idengan seddan alih-alih mengarahkan ke file baru dan mengganti yang asli dengan itu jika sedutilitas keluar tanpa kesalahan.
Kusalananda
2
Ataugrep -vx '[^C]*C[^C]*'
Stéphane Chazelas
@ Kusalananda Tapi kemudian Anda mungkin juga menggunakan grepkarena lebih jelas dan lebih kuat (khususnya, sedmemiliki kode keluar kurang informatif).
tripleee
4

Alat POSIX untuk mengedit skrip file (daripada mencetak konten yang dimodifikasi ke standar keluar) adalah ex.

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

Tentu saja Anda dapat menggunakansed -i jika versi Sed Anda mendukungnya, perlu diketahui bahwa itu tidak portabel jika Anda sedang menulis skrip yang dimaksudkan untuk dijalankan pada berbagai jenis sistem.


David Foerster bertanya dalam komentar:

Apakah ada alasan mengapa Anda menggunakan printfdan bukan echoatau sesuatu seperti itu ex -c COMMAND?

Jawab: Ya.

Untuk printfvs. echoitu adalah masalah portabilitas; lihat Mengapa printf lebih baik daripada gema? Dan juga lebih mudah untuk memotong garis baru antara perintah yang digunakan printf.

Untuk printf ... | exvs. ex -c ..., ini masalah penanganan kesalahan. Untuk perintah khusus ini tidak masalah, tetapi secara umum itu penting; misalnya, coba pakai

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

dalam naskah. Kontras dengan yang berikut ini:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

Yang pertama akan hang dan menunggu input; yang kedua akan keluar ketika EOF diterima oleh experintah, sehingga skrip akan berlanjut. Ada beberapa solusi alternatif, seperti s///e, tetapi tidak ditentukan oleh POSIX. Saya lebih suka menggunakan formulir portabel, yang ditunjukkan di atas.

Untuk gperintah, harus ada baris baru di akhir, dan saya lebih suka menggunakan printfuntuk membungkus perintah daripada menyematkan baris baru dalam tanda kutip tunggal.

Wildcard
sumber
1
Apakah ada alasan mengapa Anda menggunakan printfdan bukan echoatau sesuatu seperti itu ex -c COMMAND?
David Foerster
@ DavidFoerster, ya. Saya mulai menjawab Anda dalam komentar tetapi bertambah panjang, jadi saya menambahkannya ke jawabannya.
Wildcard
Terima kasih dan +1! Saya tahu tentang printfvs. echo(meskipun saya biasanya lebih suka echoketika argumen tersebut dikodekan) tetapi saya belum menggunakannya exsecara luas sejauh ini.
David Foerster
2

Berikut adalah beberapa opsi menggunakan perl.

Karena Anda hanya mencocokkan satu karakter, Anda dapat menggunakan tr/C//(terjemahan, tanpa penggantian), untuk mengembalikan jumlah kecocokan dari C:

perl -lne 'print if tr/C// != 1' file

Secara umum, jika Anda ingin mencocokkan string multi-karakter atau ekspresi reguler, maka Anda dapat menggunakan ini:

perl -lne 'print if (@m = /C/g) != 1' file

Ini menetapkan kecocokan dari ekspresi reguler /C/gke daftar @mdan mencetak garis ketika panjang daftar itu tidak 1.

The -iswitch dapat ditambahkan ke edit "di tempat".

Tom Fenech
sumber
2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'

sumber
Perhatikan bahwa ia menganggap GNU sed, t #...biasanya bercabang ke label yang disebut #...di sebagian besar sedimplementasi lainnya .
Stéphane Chazelas
Bahkan !bGNU sed karena cabang tidak suka apa pun kecuali label atau baris baru setelah itu.
Ya, b, t, :, }(dan r file, w file...) tidak dapat memiliki perintah setelah mereka pada baris yang sama. Anda juga dapat menggunakan -eopsi terpisah .
Stéphane Chazelas
Opsi perl Anda tidak menghasilkan output yang benar. Saya kira Anda lupa menambahkan gpengubah.
Tom Fenech
@ TomFenech Anda benar. Saya memperbaikinya. Terima kasih.
1

Bagi siapa pun yang ingin awksecara khusus, saya akan menawarkan

awk '/C[^C]*C/{next}//{print}'

lewati garis jika cocok dengan pola, cetak sebaliknya. Anda tidak benar-benar perlu {print}, Anda dapat menggunakan //dan mencetak standar, tapi saya pikir itu lebih jelas dijabarkan.

Pikiran pertama saya adalah menggunakan egrep -vdengan pola yang sama, tetapi itu tidak benar-benar menjawab pertanyaan yang diajukan.

nigel222
sumber
1
Apa gunanya mencocokkan sesuatu setelah itu {next}? Katakan saja awk '/pattern/ {next} 1'dan semua garis yang tidak cocok dengan pola akan dicetak. Atau, lebih baik, awk '!/pattern/'untuk langsung mencetaknya.
fedorqui
@fedorqui poin bagus tentang !/pattern/(yang entah bagaimana menyelinap di pikiranku) tapi aku lebih suka melihat penjelasan sendiri //{print}daripada samar 1. Asumsikan kompetensi dan kelancaran paling sedikit dari orang berikutnya untuk menjaga kode Anda, konsisten dengan tidak menjadikannya kurang efisien atau efektif.
nigel222