Bagaimana cara menghapus baris terakhir dari semua file dari direktori?

17

Saya memiliki banyak file teks dalam direktori, dan saya ingin menghapus baris terakhir dari setiap file dalam direktori.

Bagaimana saya bisa melakukannya?

ThehalfarisedPheonix
sumber
6
Apa yang sudah kamu coba? unix.stackexchange.com/help/how-to-ask : "Berbagi penelitian Anda membantu semua orang. Beri tahu kami apa yang Anda temukan dan mengapa itu tidak memenuhi kebutuhan Anda. Ini menunjukkan bahwa Anda telah meluangkan waktu untuk mencoba membantu diri sendiri , ini menyelamatkan kami dari mengulangi jawaban yang jelas, dan yang terpenting, ini membantu Anda mendapatkan jawaban yang lebih spesifik dan relevan! "
Patrick
Mengapa pemikiran seseorang yang secara tidak sengaja menerapkannya pada / etc memiliki kualitas yang sangat istimewa dari induksi ngeri :)
rackandboneman

Jawaban:

4

Jika Anda memiliki akses ke vim, Anda dapat menggunakan:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done
R Sahu
sumber
16

Anda dapat menggunakan oneliner yang bagus ini jika Anda memiliki GNU sed.

 sed -i '$ d' ./*

Ini akan menghapus baris terakhir dari setiap file yang tidak disembunyikan di direktori saat ini. Beralih -iuntuk GNU sedberarti beroperasi di tempat dan '$ d'memerintahkan seduntuk menghapus baris terakhir ( $artinya terakhir dan dberarti menghapus).

StefanR
sumber
3
Ini akan memunculkan kesalahan (dan tidak melakukan apa-apa) jika folder tersebut berisi apa pun selain file biasa, misalnya folder lain ...
Najib Idrissi
1
@StefanR Anda menggunakan -iyang merupakan GNUism, jadi ini bisa diperdebatkan, tapi saya akan kehilangan janggut lama saya jika saya gagal menunjukkan bahwa beberapa versi yang lebih lama sedtidak memungkinkan Anda untuk meletakkan spasi kosong apa pun di antara $dan d(atau, secara umum, antara pola dan perintah).
zwol
1
@ zwol Seperti yang saya tulis, ini akan menghasilkan kesalahan , bukan peringatan, dan sed akan menyerah begitu mencapai file itu (setidaknya dengan versi sed yang saya miliki). File selanjutnya tidak akan diproses. Membuang pesan kesalahan akan menjadi ide yang buruk karena Anda bahkan tidak tahu itu telah terjadi! Dengan zsh yang bisa Anda gunakan *(.)untuk glob untuk file biasa, saya tidak tahu tentang shell lainnya.
Najib Idrissi
@NajibIdrissi Hmm, kamu benar. Itu mengejutkan saya; Saya berharap untuk mengeluh tentang direktori tetapi kemudian pergi ke file berikutnya pada baris perintah. Bahkan, saya pikir saya akan melaporkan itu sebagai bug.
zwol
@don_crissti Saya punya GNU sed v4.3 juga ... Saya tidak tahu harus bilang apa, saya hanya menguji lagi. gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Najib Idrissi
11

Jawaban lain semua memiliki masalah jika direktori berisi sesuatu selain file biasa, atau file dengan spasi / baris baru dalam nama file. Berikut ini sesuatu yang berfungsi:

find "$dir" -type f -exec sed -i '$d' '{}' '+'
  • find "$dir" -type f: cari file di direktori $dir
    • -type f yang merupakan file biasa;
    • -exec jalankan perintah pada setiap file yang ditemukan
    • sed -i: mengedit file di tempat;
    • '$d': delete ( d) baris terakhir ( $).
    • '+': memberitahu find untuk terus menambahkan argumen sed(sedikit lebih efisien daripada menjalankan perintah untuk setiap file secara terpisah, terima kasih kepada @zwol).

Jika Anda tidak ingin turun ke subdirektori, maka Anda dapat menambahkan argumen -maxdepth 1ke find.

Najib Idrissi
sumber
1
Hmm, tetapi tidak seperti jawaban lain, ini turun ke subdirektori. (Juga, dengan versi saat findini, ini ditulis dengan lebih efisien find $dir -type f -exec sed -i '$d' '{}' '+'.)
zwol
@ zwol Terima kasih, saya telah menambahkannya ke dalam jawabannya.
Najib Idrissi
-print0tidak hadir dalam perintah penuh, mengapa memasukkannya ke dalam penjelasan?
Ruslan
1
Juga, -depth 0tidak berfungsi (findutils 4.4.2), mestinya sebaliknya -maxdepth 1.
Ruslan
@Ruslan Saya memiliki versi pertama di mana saya menggunakan xarg tetapi kemudian diingat -exec.
Najib Idrissi
9

Menggunakan GNU sed -i '$d'berarti membaca file lengkap dan membuat salinannya tanpa baris terakhir, sementara itu akan jauh lebih efisien untuk hanya memotong file di tempat (setidaknya untuk file besar).

Dengan GNU truncate, Anda dapat melakukan:

for file in ./*; do
  [ -f "$file" ] &&
    length=$(tail -n 1 "$file" | wc -c) &&
    [ "$length" -gt 0 ] &&
    truncate -s "-$length" "$file"
done

Jika file relatif kecil, itu mungkin akan kurang efisien karena menjalankan beberapa perintah per file.

Perhatikan bahwa untuk file yang berisi byte tambahan setelah karakter baris baru terakhir (setelah baris terakhir) atau dengan kata lain jika mereka memiliki baris terakhir yang tidak dibatasi , maka tergantung pada tailimplementasinya, tail -n 1akan mengembalikan hanya byte tambahan tersebut (seperti GNU tail), atau baris terakhir (dibatasi dengan benar) dan byte tambahan itu.

Stéphane Chazelas
sumber
Anda perlu |wc -cdi tailpanggilan? (atau a ${#length})
Jeff Schaller
@JeffSchaller. Ups. wc -c memang dimaksudkan. ${#length}tidak akan berfungsi karena menghitung karakter, bukan byte dan $(...)akan menghapus karakter baris baru tailing sehingga ${#...}akan dimatikan oleh satu bahkan jika semua karakter single-byte.
Stéphane Chazelas
6

Pendekatan yang lebih portabel:

for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done

Saya tidak berpikir ini perlu penjelasan apa pun ... kecuali mungkin dalam kasus dini sama $dkarena edsecara default memilih baris terakhir.
Ini tidak akan mencari secara rekursif dan tidak akan memproses file tersembunyi (alias dotfiles).
Jika Anda ingin mengeditnya juga lihat Cara mencocokkan * dengan file tersembunyi di dalam direktori

don_crissti
sumber
Bagus! Jika Anda mengubah [[]]ke []maka akan sepenuhnya POSIX compliant. ( [[ ... ]]is a Bashism.)
Wildcard
@Wildcard - terima kasih, berubah (walaupun [[bukan bashism )
don_crissti
Saya harus mengatakan non-POSIX-isme. :)
Wildcard
3

Satu-liner yang sesuai dengan POSIX untuk semua file secara rekursif dimulai pada direktori saat ini, termasuk dot-file:

find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Hanya untuk .txtfile, non-rekursif:

find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Lihat juga:

Wildcard
sumber