Ya, kami melihat sejumlah hal seperti:
while read line; do
echo $line | cut -c3
done
Atau lebih buruk:
for line in `cat file`; do
foo=`echo $line | awk '{print $2}'`
echo whatever $foo
done
(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:
cut -c4-5 < in | tr a b > out
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:
while read line; do
echo $line | cut -c3
done < file
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 untuk echo
dan cut
alat, ambil dari laci, panggil mereka, cuci, keringkan, masukkan kembali ke dalam laci dan seterusnya.
Beberapa alat tersebut ( read
dan echo
) dibangun di sebagian besar shell, tapi itu hampir tidak membuat perbedaan di sini karena echo
dan cut
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()
atau fputs()
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. Jadi read
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 dan bash
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:
while read line; do
echo ${line:2:1}
done
(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:
foo\/bar \
baz
biz
read line
akan menyimpan "foo/bar baz"
ke dalam $line
, tidak " foo\/bar \"
seperti yang Anda harapkan.
Untuk membaca sebuah baris, Anda sebenarnya perlu:
IFS= read -r line
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 di printf
sini sebagai gantinya.
Dan tentu saja, ada yang khas lupa mengutip variabel Anda yang semua orang jatuh ke dalamnya. Jadi lebih dari itu:
while IFS= read -r line; do
printf '%s\n' "$line" | cut -c3
done < file
Sekarang, beberapa peringatan lagi:
- kecuali
zsh
, itu tidak berfungsi jika input berisi karakter NUL sementara setidaknya utilitas teks GNU tidak akan memiliki masalah.
- jika ada data setelah baris baru terakhir, itu akan dilewati
- di dalam loop, stdin diarahkan sehingga Anda perlu memperhatikan bahwa perintah di dalamnya tidak membaca dari stdin.
- untuk perintah dalam loop, kami tidak memperhatikan apakah mereka berhasil atau tidak. Biasanya, kondisi kesalahan (disk penuh, kesalahan baca ...) akan ditangani dengan buruk, biasanya lebih buruk daripada dengan yang setara yang benar .
Jika kami ingin mengatasi beberapa masalah di atas, itu menjadi:
while IFS= read -r line <&3; do
{
printf '%s\n' "$line" | cut -c3 || exit
} 3<&-
done 3< file
if [ -n "$line" ]; then
printf '%s' "$line" | cut -c3 || exit
fi
Itu menjadi semakin tidak terbaca.
Ada sejumlah masalah lain dengan mengirimkan data ke perintah melalui argumen atau mengambil hasilnya dalam variabel:
- pembatasan ukuran argumen (beberapa implementasi utilitas teks memiliki batas di sana juga, meskipun efek yang dicapai umumnya kurang bermasalah)
- karakter NUL (juga masalah dengan utilitas teks).
- argumen diambil sebagai opsi saat mereka memulai dengan
-
(atau +
terkadang)
- berbagai kebiasaan berbagai perintah yang biasanya digunakan dalam loop seperti
expr
, test
...
- operator manipulasi teks (terbatas) dari berbagai shell yang menangani karakter multi-byte dengan cara yang tidak konsisten.
- ...
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
yes
menulis ke file begitu cepat?