shellcheck đang khuyên không nên sử dụng basename: tại sao?


25

Tôi đang thử shellcheck .

Tôi có một cái gì đó như thế

basename "${OPENSSL}" 

và tôi nhận được gợi ý sau

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

Từ quan điểm thực tế tôi thấy không có sự khác biệt

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

basenametrong thông số kỹ thuật POSIX , tôi không biết lý do tại sao nó nên được thực hành tốt nhất. Có gợi ý nào không?


8
Nó tạo ra một quy trình mới khi không cần thiết.
jordanm

@jordanm đủ công bằng ... Tôi không nghĩ về hiệu quả.
Matteo

3
@jordanm Mặt khác, nó hoạt động với các shell khác ngoài bash
Matteo

1
@JosephR. đó là những gì tôi nghĩ nhưng chỉ phát hiện ra rằng nó không hoạt động csh. Tôi đoán cshkhông phải là POSIX.
terdon

3
@terdon - csh rất xa POSIX.
jordanm

Câu trả lời:


25

Đó không phải là về hiệu quả - đó là về tính đúng đắn. basenamesử dụng các dòng mới để phân định tên tệp mà nó in ra. Trong trường hợp thông thường khi bạn chỉ truyền một tên tệp, nó sẽ thêm một dòng mới vào đầu ra của nó. Vì tên tệp có thể chứa dòng mới, điều này gây khó khăn cho việc xử lý chính xác các tên tệp này.

Nó phức tạp hơn nữa bởi thực tế là mọi người thường sử dụng basenamenhư thế này : "$(basename "$file")". Điều này làm cho mọi thứ trở nên khó khăn hơn, bởi vì $(command)dải tất cả các dòng mới từ command. Xem xét các trường hợp không thể $filekết thúc với một dòng mới. Sau đó basenamesẽ thêm một dòng mới, nhưng "$(basename "$file")"sẽ loại bỏ cả hai dòng mới, để lại cho bạn một tên tệp không chính xác.

Một vấn đề khác basenamelà nếu $filebắt đầu bằng một -dấu gạch ngang (dấu gạch ngang), nó sẽ được hiểu là một tùy chọn. Cái này rất dễ sửa:$(basename -- "$file")

Cách sử dụng mạnh mẽ basenamelà:

# 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%??}"

Một cách khác là sử dụng ${file##*/}, dễ hơn nhưng có lỗi của riêng nó. Đặc biệt, nó sai trong trường hợp $file/hay foo/.


2
Điểm rất tốt, +1. Vui mừng khi OP chấp nhận điều này thay vì của tôi.
terdon

2
Counterpoint: nếu $filefoo/gì? Nếu nó là /gì?
Gilles 'SO- ngừng trở nên xấu xa'

2
@Gilles: Bạn nói đúng. Tôi đang bắt đầu nghĩ rằng basenamecách tiếp cận tốt hơn sau tất cả, cũng như hacky. Các lựa chọn thay thế tốt nhất tôi có thể đưa ra là ${${${file}%%/#}##*/}, và [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1]cả hai đều là đặc trưng của zsh và cả hai đều không xử lý /chính xác.
Matt

@terdon Cảm ơn bạn đã không nhận nó một cách cá nhân :-). Các tập tin với dòng mới không phổ biến nhưng Matt có một điểm. Tất nhiên sử dụng thay thế biến cũng hiệu quả hơn.
Matteo

16

Các dòng có liên quan trong shellcheckcủa mã nguồn là:

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 ()

Không có lời giải thích nào được đưa ra một cách rõ ràng nhưng dựa trên tên của hàm ( checkNeedlessCommands) có vẻ như @jordanm hoàn toàn đúng và điều đó gợi ý bạn nên tránh tiến trình mới.


7
Có thể nguồn sẽ ở bên bạn :)
Joseph R.

3

dirname, basename, readlinkVv (nhờ @Marco - điều này được khắc phục) có thể tạo ra vấn đề di khi an ninh trở nên quan trọng (yêu cầu an ninh của con đường). Nhiều hệ thống (như Fedora Linux) đặt nó ở /bintrong khi các hệ thống khác (như Mac OSX) đặt nó tại /usr/bin. Sau đó, có Bash trên Windows, ví dụ cygwin, msys và những người khác. Luôn luôn tốt hơn để giữ Bash tinh khiết, khi có thể. (mỗi bình luận @Marco)

BTW, cảm ơn con trỏ tới shellcheck, tôi chưa từng thấy điều đó trước đây.


1
1) Ý nghĩa của bạn là gì Bạn có thể xây dựng? 2) OP hoàn toàn không đề cập đến dirname. 3) Các tiện ích cốt lõi cơ bản nên có trong PATH, bất cứ nơi nào chúng được lưu trữ. Tôi chưa thấy một hệ thống nào basenamekhông có trong ĐƯỜNG. 4) Giả sử bash có sẵn là một vấn đề di động. Tốt hơn hết là tránh xa bash và sử dụng shell POSIX khi cần tính di động.
Marco

@Marco Cảm ơn bạn đã chỉ ra những vấn đề này. Có bạn chính xác rằng các tiện ích đang trên đường dẫn. Nhưng khi người ta muốn cung cấp bảo mật bổ sung cho tập lệnh Bash, thì đó là cách tốt để cung cấp đường dẫn tuyệt đối. Vì vậy, việc gọi '/ bin / basename' sẽ hoạt động cho các hệ thống RedHat, nhưng nó sẽ tạo ra lỗi trên máy Mac. Điều này được giải thích rõ hơn trong Bash Cookbook nơi có khoảng một phần tư trong số 600 trang dành cho chủ đề này. Chúng tôi đã thực hiện một nỗ lực sơ bộ để giải quyết các vấn đề bảo mật trong nguồn an toàn-lib miễn phí của chúng tôi .
AsymLabs

@Marco Điểm 4 là một nhận xét hợp lệ nhưng câu hỏi (OP) bắt đầu bằng và được viết xung quanh shellcheck , được thiết kế để kiểm tra cả hai tập lệnh sh / bash. Do đó, chúng tôi phải giả định rằng nó không hoàn toàn là Posix theo mặc định.
AsymLabs

@Marco Bảo mật của biến môi trường đường dẫn có thể bị xâm phạm dễ dàng. Ví dụ, tôi đã rất ngạc nhiên khi thấy vài năm trước rằng Ruby RVM, được cài đặt cục bộ, theo mặc định đã đặt đường dẫn của nó trước đường dẫn hệ thống.
AsymLabs

OP đặc biệt đề cập đến POSIX và câu hỏi được gắn thẻ posixvà không bash. Tôi không tìm thấy bất kỳ chỉ số nào cho thấy OP yêu cầu giải pháp bash. Tuyên bố của bạn Luôn luôn tốt hơn để giữ thuần Bash Bash là hoàn toàn sai, tôi xin lỗi.
Marco
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.