Cả hai đều có những điều kỳ quặc, thật không may.
Cả hai đều được POSIX yêu cầu, vì vậy sự khác biệt giữa chúng không phải là vấn đề về tính di động¹.
Cách đơn giản để sử dụng các tiện ích là
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Lưu ý các dấu ngoặc kép xung quanh các thay thế thay đổi, như mọi khi, và cả --
sau lệnh, trong trường hợp tên tệp bắt đầu bằng dấu gạch ngang (nếu không các lệnh sẽ hiểu tên tệp là một tùy chọn). Điều này vẫn thất bại trong trường hợp một cạnh, điều này rất hiếm nhưng có thể bị ép buộc bởi một người dùng độc hại²: thay thế lệnh sẽ loại bỏ các dòng mới. Vì vậy, nếu một tên tệp được gọi foo/bar
thì base
sẽ được đặt thành bar
thay vì bar
. Một cách giải quyết là thêm một ký tự không phải dòng mới và loại bỏ nó sau khi thay thế lệnh:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Với việc thay thế tham số, bạn không gặp phải các trường hợp cạnh liên quan đến việc mở rộng các ký tự lạ, nhưng có một số khó khăn với ký tự gạch chéo. Một điều hoàn toàn không phải là trường hợp biên là việc tính toán phần thư mục yêu cầu mã khác nhau cho trường hợp không có /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Trường hợp cạnh là khi có một dấu gạch chéo (bao gồm cả trường hợp của thư mục gốc, tất cả là dấu gạch chéo). Các lệnh basename
và dirname
tước bỏ dấu gạch chéo trước khi chúng thực hiện công việc của mình. Không có cách nào để loại bỏ các dấu gạch chéo trong một lần nếu bạn sử dụng các cấu trúc POSIX, nhưng bạn có thể thực hiện theo hai bước. Bạn cần quan tâm đến trường hợp khi đầu vào không có gì ngoài dấu gạch chéo.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Nếu bạn tình cờ biết rằng bạn không ở trong trường hợp cạnh (ví dụ: find
kết quả không phải là điểm bắt đầu luôn chứa một phần thư mục và không có dấu vết /
) thì thao tác chuỗi mở rộng tham số là đơn giản. Nếu bạn cần đối phó với tất cả các trường hợp cạnh, các tiện ích sẽ dễ sử dụng hơn (nhưng chậm hơn).
Đôi khi, bạn có thể muốn đối xử foo/
như thế foo/.
hơn là thích foo
. Nếu bạn đang hành động trên một mục nhập thư mục thì foo/
được cho là tương đương foo/.
, không foo
; điều này tạo ra sự khác biệt khi foo
là một liên kết tượng trưng đến một thư mục: foo
có nghĩa là liên kết tượng trưng, foo/
có nghĩa là thư mục đích. Trong trường hợp đó, tên cơ sở của một đường dẫn có dấu gạch chéo là thuận lợi .
và đường dẫn có thể là tên thư mục riêng của nó.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
Phương pháp nhanh và đáng tin cậy là sử dụng zsh với các bộ sửa đổi lịch sử của nó (dải đầu tiên này cắt các dấu gạch chéo, giống như các tiện ích):
dir=$filename:h base=$filename:t
¹ Trừ khi bạn đang sử dụng các vỏ POSIX trước như Solaris 10 và cũ hơn /bin/sh
(thiếu các tính năng thao tác chuỗi mở rộng tham số trên các máy vẫn đang được sản xuất - nhưng luôn có vỏ POSIX được gọi sh
trong cài đặt, chỉ có nó /usr/xpg4/bin/sh
, không phải vậy /bin/sh
).
² Ví dụ: gửi tệp được gọi foo
đến dịch vụ tải lên tệp không bảo vệ chống lại điều này, sau đó xóa tệp đó và foo
thay vào đó sẽ bị xóa
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Tôi đã đọc kỹ và tôi không nhận thấy bạn đề cập đến bất kỳ nhược điểm nào.