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

Mat*_*teo 28 shell filenames posix basename

我正在尝试shellcheck

我有类似的东西

basename "${OPENSSL}" 
Run Code Online (Sandbox Code Playgroud)

我得到以下建议

Use parameter expansion instead, such as ${var##*/}.
Run Code Online (Sandbox Code Playgroud)

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

$ export OPENSSL=/opt/local/bin/openssl
$ basename ${OPENSSL}
openssl
$ echo ${OPENSSL##*/}
openssl
Run Code Online (Sandbox Code Playgroud)

由于basenamePOSIX 规范中,我不知道为什么它应该是最佳实践。任何提示?

Mat*_*att 28

这不是关于效率——而是关于正确性。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%??}"
Run Code Online (Sandbox Code Playgroud)

另一种方法是使用${file##*/},它更容易,但有其自身的错误。特别是在$fileis/或的情况下是错误的foo/

  • 很好的分数,+1。很高兴 OP 接受了这个而不是我的。 (2认同)
  • 反点:如果`$file` 是`foo/` 会怎样?如果是`/`怎么办? (2认同)
  • @吉尔斯:你说得对。我开始认为 `basename` 方法毕竟更好,尽管它很笨拙。我能想到的最好的选择是 `${${${file}%%/#}##*/}` 和 `[[ $file =~ "([^/]*)/*$" ] ] && printf "%s" $match[1]`,两者都是 zsh 特定的,并且都不能正确处理 `/`。 (2认同)

ter*_*don 17

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 ()
Run Code Online (Sandbox Code Playgroud)

没有明确给出解释,但根据函数的名称 ( checkNeedlessCommands),看起来 @jordanm 是非常正确的,它建议您避免分叉新进程。

  • 愿源与你同在:) (7认同)