shellcheck menyarankan untuk tidak menggunakan nama merek: mengapa?

25

Saya mencoba shellcheck .

Saya punya sesuatu seperti itu

basename "${OPENSSL}" 

dan saya mendapatkan saran berikut

Use parameter expansion instead, such as ${var##*/}.

Dari sudut pandang praktis saya tidak melihat perbedaan

$ export OPENSSL=/opt/local/bin/openssl
$ basename ${OPENSSL}
openssl
$ echo ${OPENSSL##*/}
openssl

Karena basenameada dalam spesifikasi POSIX , saya tidak punya alasan mengapa itu harus menjadi praktik terbaik. Ada petunjuk?

Matteo
sumber
8
Itu memalsukan proses baru ketika tidak perlu.
jordanm
@jordanm cukup adil ... Saya tidak memikirkan efisiensi.
Matteo
3
@jordanm Di sisi lain itu berfungsi dengan pada kerang selain bash
Matteo
1
@ JosephephR. itulah yang saya pikirkan tetapi baru tahu bahwa itu tidak berhasil csh. Saya kira cshitu bukan POSIX.
terdon
3
@terdon - csh sangat jauh dari POSIX.
jordanm

Jawaban:

25

Ini bukan tentang efisiensi - ini tentang kebenaran. basenamemenggunakan baris baru untuk membatasi nama file yang dicetaknya. Dalam kasus yang biasa ketika Anda hanya melewatkan satu nama file, itu menambahkan baris tambahan ke outputnya. Karena nama file mungkin mengandung baris baru sendiri, ini membuatnya sulit untuk menangani nama file ini dengan benar.

Ini lebih rumit oleh fakta bahwa orang biasanya menggunakan basenameseperti ini: "$(basename "$file")". Ini membuat segalanya semakin sulit, karena semua$(command) strip mengikuti garis baru . Pertimbangkan kasus tidak terduga yang berakhir dengan baris baru. Kemudian akan menambahkan baris baru tambahan, tetapi akan menghapus kedua baris baru, membuat Anda dengan nama file yang salah.command$filebasename"$(basename "$file")"

Masalah lain dengan basenameadalah bahwa jika $filediawali dengan -(minus alias minus), itu akan ditafsirkan sebagai opsi. Yang ini mudah diperbaiki:$(basename -- "$file")

Cara penggunaan yang kuat basenameadalah ini:

# A file with three trailing newlines.
file=$'/tmp/evil\n\n\n'

# Add an 'x' so we can tell where $file's newlines end and basename's begin.
file_x="$(basename -- "$file"; printf x)"

# Strip off two trailing characters: the 'x' added by us and the newline added by basename. 
base="${file_x%??}"

Alternatifnya adalah menggunakan ${file##*/}, yang lebih mudah tetapi memiliki bug sendiri. Secara khusus, itu salah dalam kasus di mana $fileadalah /atau foo/.

Mat
sumber
2
Poin yang sangat bagus, +1. Senang OP menerima ini, bukan milikku.
terdon
2
Counterpoint: bagaimana jika $fileitu foo/? Bagaimana jika itu /?
Gilles 'SANGAT berhenti menjadi jahat'
2
@Gilles: Anda benar. Saya mulai berpikir bahwa basenamependekatannya lebih baik, sama hebohnya. Alternatif terbaik yang bisa saya temukan adalah ${${${file}%%/#}##*/}dan [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1], keduanya zsh-specific dan tidak ada yang menangani /dengan benar.
Matt
@terdon Terima kasih karena Anda tidak tersinggung :-). File dengan baris baru tidak umum tetapi Matt ada benarnya. Tentu saja menggunakan subtitusi variabel juga lebih efisien.
Matteo
16

Garis-garis yang relevan di shellcheck's kode sumber adalah:

checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "dirname" =
    style id "Use parameter expansion instead, such as ${var%/*}."
checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "basename" =
    style id "Use parameter expansion instead, such as ${var##*/}."
checkNeedlessCommands _ = return ()

Tidak ada penjelasan yang diberikan secara eksplisit tetapi berdasarkan nama fungsi ( checkNeedlessCommands) sepertinya @jordanm cukup benar dan ini menyarankan Anda menghindari forking proses baru.

terdon
sumber
7
Semoga sumber bersamamu :)
Joseph R.
3

dirname, basename, readlinkDll (terima kasih @Marco - ini dikoreksi) dapat membuat masalah portabilitas ketika keamanan menjadi penting (yang membutuhkan keamanan jalan). Banyak sistem (seperti Fedora Linux) menempatkannya di /binsedangkan yang lain (seperti Mac OSX) menempatkannya di /usr/bin. Lalu ada Bash di Windows, misalnya cygwin, msys, dan lainnya. Selalu lebih baik untuk tetap menjadi Bash murni, jika memungkinkan. (per komentar @Marco)

BTW, terima kasih atas pointer ke shellcheck, saya belum pernah melihat itu sebelumnya.

AsymLabs
sumber
1
1) Apa yang Anda maksud dengan "keamanan"? Bisakah Anda menguraikan? 2) OP tidak menyebutkan dirnamesama sekali. 3) Utilitas inti dasar harus berada di PATH, di mana pun disimpan. Saya belum melihat sistem di mana basenametidak ada di PATH. 4) Dengan asumsi bash tersedia adalah masalah portabilitas. Itu selalu lebih baik untuk menjauh dari bash dan menggunakan shell POSIX ketika portabilitas diperlukan.
Marco
@ Mars Terima kasih telah menunjukkan masalah ini. Ya, Anda benar bahwa utilitasnya ada di jalur. Tetapi di mana seseorang ingin memberikan keamanan tambahan pada skrip Bash, praktik yang baik adalah menyediakan jalur absolut. Jadi memanggil '/ bin / basename' akan bekerja untuk sistem RedHat, tetapi itu akan menghasilkan kesalahan pada Mac. Ini lebih baik dijelaskan dalam Bash Cookbook di mana sekitar seperempat dari 600 halaman dikhususkan untuk subjek ini. Kami telah melakukan upaya kasar untuk mengatasi masalah keamanan di sana di lib- sumber bebas kami yang aman .
AsymLabs
@Marco Point 4 adalah komentar yang valid tetapi pertanyaan (OP) dimulai dengan dan ditulis di sekitar shellcheck yang dirancang untuk memeriksa kedua skrip sh / bash. Oleh karena itu kita harus mengasumsikan itu bukan Posix secara default.
AsymLabs
@Marco Keamanan variabel lingkungan jalur dapat dikompromikan dengan mudah. Sebagai contoh, saya sangat terkejut menemukan beberapa tahun yang lalu bahwa Ruby RVM, yang diinstal secara lokal, secara default menempatkan path-nya sebelum path sistem.
AsymLabs
OP secara khusus menyebutkan POSIX dan pertanyaannya ditandai posixdan tidak bash. Saya gagal menemukan indikator bahwa OP memerlukan solusi bash. Pernyataan Anda "Selalu lebih baik untuk tetap murni Bash" benar-benar salah, maaf.
Marco