Apakah menggunakan loop sementara untuk memproses teks secara umum dianggap praktik buruk di shell POSIX?
Seperti yang ditunjukkan oleh Stéphane Chazelas , beberapa alasan untuk tidak menggunakan shell loop adalah konseptual , keandalan , keterbacaan , kinerja , dan keamanan .
Jawaban ini menjelaskan aspek keandalan dan keterbacaan :
while IFS= read -r line <&3; do
printf '%s\n' "$line"
done 3< "$InputFile"
Untuk kinerja , while
loop dan baca sangat lambat saat membaca dari file atau pipa, karena shell baca built-in membaca satu karakter pada satu waktu.
Bagaimana dengan aspek konseptual dan keamanan ?
shell
text-processing
cuonglm
sumber
sumber
yes
menulis ke file begitu cepat?bash
, ia membaca satu ukuran buffer sekaligus, cobadash
misalnya. Lihat juga unix.stackexchange.com/q/209123/38906Jawaban:
Ya, kami melihat sejumlah hal seperti:
Atau lebih buruk:
(jangan tertawa, saya sudah melihat banyak dari mereka).
Umumnya dari pemula shell scripting. Itu adalah terjemahan harfiah yang naif dari apa yang akan Anda lakukan dalam bahasa imperatif seperti C atau python, tapi itu bukan cara Anda melakukan hal-hal dalam shell, dan contoh-contoh itu sangat tidak efisien, sama sekali tidak dapat diandalkan (berpotensi menyebabkan masalah keamanan), dan jika Anda pernah mengelola untuk memperbaiki sebagian besar bug, kode Anda menjadi tidak terbaca.
Secara konseptual
Di C atau sebagian besar bahasa lain, blok bangunan hanya satu tingkat di atas instruksi komputer. Anda memberi tahu prosesor Anda apa yang harus dilakukan dan kemudian apa yang harus dilakukan selanjutnya. Anda mengambil prosesor Anda dengan tangan dan mengelolanya: Anda membuka file itu, Anda membaca banyak byte, Anda melakukan ini, Anda melakukannya dengan itu.
Kerang adalah bahasa tingkat yang lebih tinggi. Orang mungkin mengatakan itu bahkan bukan bahasa. Mereka di depan semua penerjemah baris perintah. Pekerjaan dilakukan oleh perintah-perintah yang Anda jalankan dan shell hanya dimaksudkan untuk mengaturnya.
Salah satu hal hebat yang diperkenalkan Unix adalah pipa dan aliran stdin / stdout / stderr default yang ditangani semua perintah secara default.
Dalam 45 tahun, kami tidak menemukan yang lebih baik dari API untuk memanfaatkan kekuatan perintah dan meminta mereka bekerja sama untuk suatu tugas. Itu mungkin alasan utama mengapa orang masih menggunakan kerang saat ini.
Anda punya alat pemotong dan alat transliterasi, dan Anda bisa melakukannya:
Shell hanya melakukan pipa ledeng (membuka file, mengatur pipa, menjalankan perintah) dan ketika semuanya siap, itu hanya mengalir tanpa shell melakukan apa pun. Alat melakukan pekerjaan mereka secara bersamaan, efisien dengan kecepatan mereka sendiri dengan buffering yang cukup sehingga tidak ada yang menghalangi yang lain, itu hanya indah dan sederhana.
Meminta alat memiliki biaya (dan kami akan mengembangkannya pada titik kinerja). Alat-alat itu dapat ditulis dengan ribuan instruksi dalam C. Suatu proses harus dibuat, alat itu harus dimuat, diinisialisasi, kemudian dibersihkan, proses dihancurkan dan menunggu.
Memohon
cut
seperti membuka laci dapur, mengambil pisau, menggunakannya, mencuci, mengeringkannya, memasukkannya kembali ke dalam laci. Saat kamu melakukan:Ini seperti untuk setiap baris file, mendapatkan
read
alat dari laci dapur (yang sangat canggung karena tidak dirancang untuk itu ), membaca baris, mencuci alat baca Anda, memasukkannya kembali ke dalam laci. Kemudian jadwalkan pertemuan untukecho
dancut
alat, ambil dari laci, panggil mereka, cuci, keringkan, masukkan kembali ke dalam laci dan seterusnya.Beberapa alat tersebut (
read
danecho
) dibangun di sebagian besar shell, tapi itu hampir tidak membuat perbedaan di sini karenaecho
dancut
masih perlu dijalankan dalam proses terpisah.Ini seperti memotong bawang tetapi mencuci pisau Anda dan memasukkannya kembali ke laci dapur di antara setiap irisan.
Di sini cara yang jelas adalah mengambil
cut
alat Anda dari laci, mengiris bawang Anda dan memasukkannya kembali ke dalam laci setelah seluruh pekerjaan selesai.TKI, dalam shell, terutama untuk memproses teks, Anda memanggil utilitas sesedikit mungkin dan meminta mereka bekerja sama untuk tugas tersebut, tidak menjalankan ribuan alat secara berurutan menunggu masing-masing untuk memulai, menjalankan, membersihkan sebelum menjalankan yang berikutnya.
Bacaan lebih lanjut dalam jawaban baik Bruce . Alat internal pemrosesan teks tingkat rendah dalam shell (kecuali mungkin untuk
zsh
) terbatas, rumit, dan umumnya tidak cocok untuk pemrosesan teks umum.Performa
Seperti yang dikatakan sebelumnya, menjalankan satu perintah memiliki biaya. Biaya besar jika perintah itu tidak dibangun, tetapi bahkan jika mereka dibangun, biayanya besar.
Dan shell tidak dirancang untuk berjalan seperti itu, mereka tidak memiliki pretensi untuk menjadi bahasa pemrograman yang performant. Mereka bukan, mereka hanya penafsir baris perintah. Jadi, sedikit optimasi yang telah dilakukan di bagian depan ini.
Juga, shell menjalankan perintah dalam proses terpisah. Blok bangunan tersebut tidak berbagi memori atau keadaan umum. Ketika Anda melakukan a
fgets()
ataufputs()
di C, itu adalah fungsi di stdio. stdio menyimpan buffer internal untuk input dan output untuk semua fungsi stdio, untuk menghindari terlalu sering melakukan panggilan sistem yang mahal.Yang sesuai bahkan builtin shell utilitas (
read
,echo
,printf
) tidak bisa melakukan itu.read
dimaksudkan untuk membaca satu baris. Jika terbaca melewati karakter baris baru, itu berarti perintah berikutnya yang Anda jalankan akan melewatkannya. Jadiread
harus membaca input satu byte pada satu waktu (beberapa implementasi memiliki optimasi jika input adalah file biasa karena mereka membaca potongan dan mencari kembali, tetapi itu hanya bekerja untuk file biasa danbash
misalnya hanya membaca 128 byte potongan yang merupakan masih jauh lebih sedikit daripada yang akan dilakukan utilitas teks).Sama di sisi output,
echo
tidak bisa hanya buffer output, itu harus langsung output karena perintah berikutnya yang Anda jalankan tidak akan berbagi buffer itu.Jelas, menjalankan perintah secara berurutan berarti Anda harus menunggu untuk itu, itu adalah tarian scheduler kecil yang memberikan kontrol dari shell dan ke alat dan kembali. Itu juga berarti (tidak seperti menggunakan alat contoh yang berjalan lama dalam pipa) bahwa Anda tidak dapat memanfaatkan beberapa prosesor pada saat yang sama saat tersedia.
Antara
while read
loop dan setara (seharusnya)cut -c3 < file
, dalam tes cepat saya, ada rasio waktu CPU sekitar 40000 dalam tes saya (satu detik versus setengah hari). Tetapi bahkan jika Anda hanya menggunakan shell builtin:(di sini dengan
bash
), itu masih sekitar 1: 600 (satu detik vs 10 menit).Keandalan / keterbacaan
Sangat sulit untuk mendapatkan kode itu dengan benar. Contoh yang saya berikan terlihat terlalu sering di alam liar, tetapi mereka memiliki banyak bug.
read
adalah alat praktis yang dapat melakukan banyak hal berbeda. Itu dapat membaca input dari pengguna, membaginya menjadi kata-kata untuk menyimpan dalam variabel yang berbeda.read line
tidak tidak membaca garis masukan, atau mungkin membaca garis dengan cara yang sangat khusus. Ini sebenarnya membaca kata-kata dari input kata-kata yang dipisahkan oleh$IFS
dan di mana backslash dapat digunakan untuk melarikan diri dari pemisah atau karakter baris baru.Dengan nilai default
$IFS
, pada input seperti:read line
akan menyimpan"foo/bar baz"
ke dalam$line
, tidak" foo\/bar \"
seperti yang Anda harapkan.Untuk membaca sebuah baris, Anda sebenarnya perlu:
Itu tidak terlalu intuitif, tapi memang begitu, ingat kerang tidak dimaksudkan untuk digunakan seperti itu.
Sama untuk
echo
.echo
memperluas urutan. Anda tidak dapat menggunakannya untuk konten sewenang-wenang seperti konten file acak. Kamu butuh diprintf
sini sebagai gantinya.Dan tentu saja, ada yang khas lupa mengutip variabel Anda yang semua orang jatuh ke dalamnya. Jadi lebih dari itu:
Sekarang, beberapa peringatan lagi:
zsh
, itu tidak berfungsi jika input berisi karakter NUL sementara setidaknya utilitas teks GNU tidak akan memiliki masalah.Jika kami ingin mengatasi beberapa masalah di atas, itu menjadi:
Itu menjadi semakin tidak terbaca.
Ada sejumlah masalah lain dengan mengirimkan data ke perintah melalui argumen atau mengambil hasilnya dalam variabel:
-
(atau+
terkadang)expr
,test
...Pertimbangan keamanan
Saat Anda mulai bekerja dengan variabel shell dan argumen untuk perintah , Anda memasukkan bidang ranjau.
Jika Anda lupa mengutip variabel Anda , lupakan akhir dari opsi penanda , bekerja di lokal dengan karakter multi-byte (norma hari ini), Anda pasti akan memperkenalkan bug yang cepat atau lambat akan menjadi kerentanan.
Ketika Anda mungkin ingin menggunakan loop.
TBD
sumber
cut
misalnya efisien.cut -f1 < a-very-big-file
efisien, seefisien yang akan Anda dapatkan jika Anda menulisnya dalam C. Apa yang sangat tidak efisien dan rawan kesalahan adalah memohoncut
untuk setiap barisa-very-big-file
dalam shell loop yang merupakan titik yang dibuat dalam jawaban ini. Itu sesuai dengan pernyataan terakhir Anda tentang menulis kode yang tidak perlu yang membuat saya berpikir mungkin saya tidak mengerti komentar Anda.Sejauh konseptual dan keterbacaan berjalan, kerang biasanya tertarik pada file. "Unit yang dapat dialamatkan" adalah file, dan "alamat" adalah nama file. Shells memiliki semua jenis metode pengujian untuk keberadaan file, tipe file, pemformatan nama file (dimulai dengan globbing). Shell memiliki sedikit primitif untuk menangani konten file. Pemrogram Shell harus menjalankan program lain untuk menangani konten file.
Karena orientasi file dan nama file, melakukan manipulasi teks di shell benar-benar lambat, seperti yang telah Anda catat, tetapi juga memerlukan gaya pemrograman yang tidak jelas dan berkerut.
sumber
Ada beberapa jawaban yang rumit, memberikan banyak detail menarik bagi para Geeks di antara kita, tetapi itu benar-benar sangat sederhana - memproses file besar dalam sebuah shell loop terlalu lambat.
Saya pikir si penanya menarik dalam jenis skrip shell yang khas, yang mungkin dimulai dengan beberapa penguraian baris perintah, pengaturan lingkungan, memeriksa file dan direktori, dan sedikit lebih banyak inisialisasi, sebelum melanjutkan ke pekerjaan utamanya: melalui pekerjaan besar: file teks berorientasi baris.
Untuk bagian pertama (
initialization
), biasanya perintah shell tidak lambat - hanya menjalankan beberapa lusin perintah, mungkin dengan beberapa loop pendek. Bahkan jika kita menulis bagian itu secara tidak efisien, biasanya akan memakan waktu kurang dari sedetik untuk melakukan semua inisialisasi itu, dan itu tidak masalah - itu hanya terjadi sekali.Tetapi ketika kita mulai memproses file besar, yang bisa memiliki ribuan atau jutaan baris, itu tidak baik untuk skrip shell untuk mengambil sebagian kecil dari yang kedua (bahkan jika itu hanya beberapa lusin milidetik) untuk setiap baris, karena itu bisa menambah hingga berjam-jam.
Saat itulah kita perlu menggunakan alat lain, dan keindahan skrip shell Unix adalah mereka membuatnya sangat mudah bagi kita untuk melakukan itu.
Alih-alih menggunakan loop untuk melihat setiap baris, kita perlu melewatkan seluruh file melalui pipa perintah . Ini berarti, alih-alih memanggil perintah ribuan atau jutaan waktu, shell memanggilnya hanya sekali. Memang benar bahwa perintah-perintah itu akan memiliki loop untuk memproses file baris demi baris, tetapi mereka bukan skrip shell dan mereka dirancang untuk menjadi cepat dan efisien.
Unix memiliki banyak alat bawaan yang canggih, mulai dari yang sederhana hingga yang kompleks, yang dapat kita gunakan untuk membangun jaringan pipa kami. Saya biasanya mulai dengan yang sederhana, dan hanya menggunakan yang lebih kompleks bila perlu.
Saya juga akan mencoba untuk tetap dengan alat standar yang tersedia di sebagian besar sistem, dan mencoba untuk menjaga penggunaan portabel saya, meskipun itu tidak selalu mungkin. Dan jika bahasa favorit Anda adalah Python atau Ruby, mungkin Anda tidak akan keberatan dengan upaya ekstra untuk memastikan itu diinstal pada setiap platform yang harus dijalankan oleh perangkat lunak Anda :-)
Alat-alat sederhana termasuk
head
,tail
,grep
,sort
,cut
,tr
,sed
,join
(ketika penggabungan 2 file), danawk
satu-liners, di antara banyak lainnya. Sungguh menakjubkan apa yang bisa dilakukan beberapa orang dengan pencocokan pola dansed
perintah.Ketika menjadi lebih kompleks, dan Anda benar-benar harus menerapkan beberapa logika untuk setiap baris,
awk
adalah pilihan yang baik - baik satu-liner (beberapa orang meletakkan skrip awk keseluruhan dalam 'satu baris', meskipun itu tidak terlalu mudah dibaca) atau dalam skrip eksternal pendek.Seperti
awk
bahasa yang ditafsirkan (seperti cangkang Anda), sungguh menakjubkan bahwa ia dapat melakukan pemrosesan baris demi baris dengan sangat efisien, tetapi dibuat khusus untuk ini dan sangat cepat.Dan kemudian ada
Perl
dan sejumlah besar bahasa scripting lain yang sangat bagus dalam memproses file teks, dan juga datang dengan banyak perpustakaan yang bermanfaat.Dan akhirnya, ada C lama yang bagus, jika Anda membutuhkan kecepatan maksimum dan fleksibilitas tinggi (walaupun pemrosesan teks agak membosankan). Tapi itu mungkin penggunaan waktu Anda yang sangat buruk untuk menulis program C baru untuk setiap tugas pemrosesan file yang berbeda yang Anda temui. Saya banyak bekerja dengan file CSV, jadi saya telah menulis beberapa utilitas umum dalam C yang dapat saya gunakan kembali di banyak proyek yang berbeda. Akibatnya, ini memperluas jangkauan 'alat Unix yang sederhana dan cepat' yang dapat saya panggil dari skrip shell saya, jadi saya dapat menangani sebagian besar proyek hanya dengan menulis skrip, yang jauh lebih cepat daripada menulis dan men-debug kode C yang dipesan lebih dahulu setiap kali!
Beberapa petunjuk terakhir:
export LANG=C
, atau banyak alat akan memperlakukan file ASCII polos-lama Anda sebagai Unicode, membuatnya jauh lebih lambatexport LC_ALL=C
jika Anda inginsort
menghasilkan pemesanan yang konsisten, terlepas dari lingkungannya!sort
data Anda, itu mungkin akan membutuhkan lebih banyak waktu (dan sumber daya: CPU, memori, disk) daripada yang lainnya, jadi cobalah untuk meminimalkan jumlahsort
perintah dan ukuran file yang mereka sortirsumber
Ya tapi...
The jawaban yang benar dari Stéphane Chazelas didasarkan pada shell konsep mendelegasikan setiap operasi teks ke binari tertentu, seperti
grep
,awk
,sed
dan lain-lain.Karena bash mampu melakukan banyak hal sendiri, menjatuhkan garpu dapat menjadi lebih cepat (bahkan daripada menjalankan penerjemah lain untuk melakukan semua pekerjaan).
Untuk contoh, lihat posting ini:
https://stackoverflow.com/a/38790442/1765658
dan
https://stackoverflow.com/a/7180078/1765658
uji dan bandingkan ...
Tentu saja
Tidak ada pertimbangan tentang input dan keamanan pengguna !
Jangan menulis aplikasi web di bawah bash !!
Tetapi untuk banyak tugas administrasi server, di mana bash dapat digunakan sebagai pengganti shell , menggunakan bash bawaan bisa sangat efisien.
Arti saya:
Alat tulis seperti bin utils bukan jenis pekerjaan yang sama dengan administrasi sistem.
Jadi bukan orang yang sama!
Di mana sysadmin harus tahu
shell
, mereka dapat menulis prototipe dengan menggunakan alat yang lebih disukai (dan paling dikenal).Jika utilitas baru ini (prototipe) benar-benar bermanfaat, beberapa orang lain dapat mengembangkan alat khusus dengan menggunakan beberapa bahasa yang lebih tepat.
sumber
bash
. (Lebih dari 3 kali lebih cepat dengan ksh93 dalam pengujian saya pada sistem saya).bash
umumnya shell paling lambat. Bahkanzsh
dua kali lebih cepat pada skrip itu. Anda juga memiliki beberapa masalah dengan variabel tanda kutip dan penggunaanread
. Jadi, Anda sebenarnya menggambarkan banyak poin saya di sini.sh
, Awk , Sed ,grep
,ed
,ex
,cut
,sort
,join
... semua dengan kehandalan lebih dari Bash atau Perl.bash
instal secara default.bash
sebagian besar hanya ditemukan di Apple MacOS dan sistem GNU (Saya kira bahwa apa yang Anda sebut distro utama ), meskipun banyak sistem juga memiliki sebagai paket opsional (sepertizsh
,tcl
,python
...)