shellcheck建议不要使用basename:为什么?


25

我正在尝试shellcheck

我有这样的东西

basename "${OPENSSL}" 

我得到以下建议

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

从实际的角度来看,我认为没有区别

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

由于basenamePOSIX规范中的内容,因此我不认为这是最佳实践。有什么提示吗?


8
它在不需要时派生一个新进程。
jordanm

@jordanm足够公平……我没想到效率。
Matteo

3
@jordanm另一方面,它可以在bash以外的其他外壳上使用
Matteo

1
@JosephR。这就是我的想法,但发现它不起作用csh。我猜那csh不是POSIX。
terdon

3
@terdon-csh离POSIX很远。
jordanm

Answers:


25

这不关乎效率,而是关乎正确性。basename使用换行符来分隔输出的文件名。在通常情况下,当您仅传递一个文件名时,它将在其输出中添加尾随换行符。由于文件名本身可能包含换行符,因此很难正确处理这些文件名。

人们通常basename像这样使用,这使情况变得更加复杂"$(basename "$file")"。这使事情变得更加困难,因为从中$(command)剥离了所有尾随的换行符command。考虑$file以换行符结尾的不太可能的情况。然后,basename将添加一个额外的换行符,但"$(basename "$file")"将同时删除两个换行符,从而使文件名不正确。

另一个问题basename是,如果$file-(破折号,也称为减号)开头,它将被解释为一个选项。这个很容易解决:$(basename -- "$file")

健壮的使用方法basename是:

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

一种替代方法是使用${file##*/},这比较容易,但是有其自身的错误。特别是在或的情况下$file是错误的。/foo/


2
很好,+ 1。乐于接受OP,而不是我的。
terdon

2
对点:那$filefoo/什么?如果是/呢?
吉尔(Gilles)'所以

2
@吉尔斯:你是对的。我开始认为这种basename方法毕竟是更好的,尽管它确实很棘手。我能想到的最好的选择是${${${file}%%/#}##*/}[[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1],这两个都是zsh特定的,并且都不能/正确处理。
马特

@terdon谢谢您没有亲自考虑它:-)。带换行符的文件并不常见,但马特有一点。当然,使用变量替换也更有效。
Matteo

16

shellcheck源代码中的相关行是:

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

没有明确给出解释,但基于函数(checkNeedlessCommands)的名称,@ jordanm看起来很正确,这建议您避免分叉新进程。


7
愿源与你同在:)
约瑟夫·

3

dirnamebasenamereadlink等(感谢@Marco -这是校正)可以创建可移植性问题时,安全性就变得很重要(需要路径的安全)。许多系统(例如Fedora Linux)将其放置在,/bin而其他系统(例如Mac OSX)将其放置在/usr/bin。然后是Windows上的Bash,例如cygwin,msys和其他。 尽可能保持纯正Bash总是更好。 (根据@Marco的评论)

顺便说一句,感谢您指向shellcheck的指针,我之前从未见过。


1
1)“安全”是什么意思?你能详细说明吗?2)OP完全没有提及dirname。3)基本的核心实用程序应该存储在PATH中。我还没有看到一个basename不在PATH中的系统。4)假设bash可用是可移植性问题。当需要可移植性时,最好远离bash并使用POSIX shell。
Marco Marco

@Marco感谢您指出这些问题。是的,您是正确的,这些实用程序都在路径上。但是,如果要为Bash脚本提供更高的安全性,则最好提供绝对路径。因此,调用'/ bin / basename'在RedHat系统上可以使用,但是在Mac上会产生错误。在Bash Cookbook中可以更好地解释这一点,其中600页中的大约四分之一专门用于此主题。我们已经进行了粗略的尝试,以解决我们的免费源secure-lib中的安全问题。
AsymLabs

@Marco Point 4是有效的注释,但问题(OP)开头并围绕shellcheck编写,shellcheck旨在检查两个sh / bash脚本。因此,我们必须假定默认情况下严格来说它不是严格的Posix。
AsymLabs

@Marco路径环境变量的安全性很容易受到损害。例如,几年前我发现在本地安装的Ruby RVM默认将其路径放在系统路径之前,我感到非常惊讶。
AsymLabs

OP专门提到POSIX,问题是带标签的posix而不是带标签的bash。我找不到任何指示OP需要bash解决方案的指标。很抱歉,您的陈述“保持纯正Bash总是更好”,这是完全错误的。
Marco Marco
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.