Sayangnya, keduanya memiliki kebiasaan masing-masing.
Keduanya diperlukan oleh POSIX, jadi perbedaan di antara mereka bukan masalah portabilitas¹.
Cara sederhana untuk menggunakan utilitas adalah
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Perhatikan tanda kutip ganda di sekitar substitusi variabel, seperti biasa, dan juga --
setelah perintah, jika nama file dimulai dengan tanda hubung (jika tidak, perintah akan menafsirkan nama file sebagai opsi). Ini masih gagal dalam satu kasus tepi, yang jarang tetapi mungkin dipaksakan oleh pengguna yang berbahaya ²: substitusi perintah menghapus baris baru. Jadi jika nama file dipanggil foo/bar
maka base
akan diatur ke bar
bukan bar
. Solusinya adalah menambahkan karakter non-baris baru dan menghapusnya setelah penggantian perintah:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Dengan penggantian parameter, Anda tidak mengalami kasus tepi yang terkait dengan perluasan karakter aneh, tetapi ada sejumlah kesulitan dengan karakter garis miring. Satu hal yang bukan kasus tepi sama sekali adalah bahwa komputasi bagian direktori memerlukan kode yang berbeda untuk kasus di mana tidak ada /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Kasus tepi adalah ketika ada garis miring (termasuk kasus direktori root, yang semuanya adalah garis miring). The basename
dan dirname
perintah menanggalkan garis miring sebelum mereka melakukan pekerjaan mereka. Tidak ada cara untuk menghapus garis miring trailing sekaligus jika Anda tetap menggunakan konstruksi POSIX, tetapi Anda bisa melakukannya dalam dua langkah. Anda harus mengurus kasing ketika input hanya terdiri dari garis miring.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Jika Anda mengetahui bahwa Anda tidak berada dalam kasus tepi (misalnya find
hasil selain dari titik awal selalu berisi bagian direktori dan tidak memiliki trailing /
) maka manipulasi string string ekspansi sangat mudah. Jika Anda perlu mengatasi semua kasing tepi, utilitas lebih mudah digunakan (tetapi lebih lambat).
Terkadang, Anda mungkin ingin memperlakukan foo/
suka foo/.
daripada suka foo
. Jika Anda bertindak pada entri direktori maka foo/
seharusnya setara dengan foo/.
, bukan foo
; ini membuat perbedaan ketika foo
tautan simbolik ke direktori: foo
berarti tautan simbolik, foo/
berarti direktori target. Dalam hal ini, nama dasar dari sebuah jalan dengan garis miring lebih menguntungkan .
, dan jalur tersebut dapat menjadi dirname sendiri.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
Metode cepat dan andal adalah menggunakan zsh dengan pengubah riwayatnya (strip pertama ini mengikuti garis miring, seperti utilitas):
dir=$filename:h base=$filename:t
¹ Kecuali jika Anda menggunakan shell pra-POSIX seperti Solaris 10 dan yang lebih lama /bin/sh
(yang tidak memiliki fitur manipulasi string ekspansi parameter pada mesin yang masih dalam produksi - tetapi selalu ada shell POSIX yang dipanggil sh
dalam instalasi, hanya saja itu /usr/xpg4/bin/sh
, bukan /bin/sh
).
² Misalnya: mengirim file yang dipanggil foo
ke layanan unggah file yang tidak melindungi dari ini, kemudian hapus dan menyebabkan foo
dihapus sebagai gantinya
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Saya membaca dengan seksama dan saya tidak melihat Anda menyebutkan kekurangan.foo/
sukafoo
, tidak sukafoo/.
, yang tidak konsisten dengan utilitas yang sesuai dengan POSIX./
jika saya membutuhkannya.find
hasil, yang selalu berisi bagian direktori dan tidak memiliki trailing/
" Tidak sepenuhnya benar,find ./
akan ditampilkan./
sebagai hasil pertama.Keduanya dalam POSIX, sehingga portabilitas "seharusnya" tidak menjadi perhatian. Penggantian shell harus dianggap berjalan lebih cepat.
Namun - itu tergantung pada apa yang Anda maksud dengan portabel. Beberapa sistem lama (yang tidak perlu) tidak mengimplementasikan fitur-fitur tersebut pada mereka
/bin/sh
(Solaris 10 dan yang lebih tua muncul di benak), sementara di sisi lain, beberapa waktu lalu, para pengembang diingatkan bahwadirname
tidak semudah portablebasename
.Sebagai referensi:
dirname - mengembalikan bagian direktori pathname (POSIX)
sh manual page pada Solaris 10 (Oracle)
Halaman manual tidak menyebutkan
##
atau%/
.Dalam mempertimbangkan portabilitas, saya harus memperhitungkan semua sistem tempat saya memelihara program. Tidak semua POSIX, jadi ada pengorbanan. Pengorbanan Anda mungkin berbeda.
sumber
Ada juga:
Hal-hal aneh seperti itu terjadi karena ada banyak penafsiran dan penguraian dan sisanya yang perlu terjadi ketika dua proses berbicara. Pergantian perintah akan menghapus baris baru. Dan NUL (meskipun itu jelas tidak relevan di sini) .
basename
dandirname
juga akan menelanjangi baris baru dalam kasus apa pun karena bagaimana lagi Anda berbicara dengan mereka? Saya tahu, membuntuti baris baru dalam nama file adalah semacam laknat, tetapi Anda tidak pernah tahu. Dan tidak masuk akal untuk menempuh jalan yang mungkin cacat ketika Anda bisa melakukan sebaliknya.Masih ...
${pathname##*/} != basename
dan juga begitu${pathname%/*} != dirname
. Perintah-perintah tersebut ditentukan untuk melakukan urutan langkah yang sebagian besar didefinisikan dengan baik untuk sampai pada hasil yang ditentukan.Spesifikasi di bawah, tetapi pertama-tama ini adalah versi terser:
Itu sepenuhnya POSIX sepenuhnya
basename
sederhanash
. Itu tidak sulit untuk dilakukan. Saya menggabungkan beberapa cabang yang saya gunakan di bawah sana karena saya bisa tanpa mempengaruhi hasil.Berikut spesifikasinya:
... mungkin komentarnya mengganggu ....
sumber
[!/]
, seperti itu[^/]
? Tetapi komentar Anda yang sepertinya tidak cocok ....basename
adalah seperangkat instruksi tentang cara melakukannya dengan shell Anda. Tetapi[!charclass]
apakah cara portabel untuk melakukannya dengan gumpalan[^class]
adalah untuk regex - dan cangkang tidak ditentukan untuk regex. Tentang pencocokan komentar ...case
filter, jadi jika saya cocok string yang berisi garis miring/
dan sebuah!/
kemudian jika pola kasus berikutnya di bawah pertandingan setiap Trailing/
garis miring sama sekali mereka hanya bisa semua garis miring. Dan satu di bawah ini yang tidak dapat memiliki trailing /Anda dapat memperoleh dorongan dari dalam proses
basename
dandirname
(Saya tidak mengerti mengapa ini bukan bawaan - jika ini bukan kandidat, saya tidak tahu apa itu) tetapi implementasinya perlu menangani hal-hal seperti:^ Dari basename (3)
dan kasus tepi lainnya.
Saya telah menggunakan:
(Implementasi terbaru saya dari GNU
basename
dandirname
menambahkan beberapa saklar baris perintah mewah khusus untuk hal-hal seperti menangani beberapa argumen atau stripping suffix, tapi itu super mudah untuk ditambahkan di shell.)Ini tidak terlalu sulit untuk membuatnya menjadi
bash
builtin baik (dengan memanfaatkan implementasi sistem yang mendasarinya), tetapi fungsi di atas tidak perlu dikompilasi, dan mereka memberikan beberapa dorongan juga.sumber
x//
dengan benar, tetapi saya telah memperbaiki untuk Anda sebelum menjawab. Saya harap itu saja.dirname a///b//c//d////e
hasila///b//c//d///
.