shellcheck está aconselhando não usar basename: por quê?

shellcheck está aconselhando não usar basename: por quê?

estou experimentandoverificação de shell.

Eu tenho algo assim

basename "${OPENSSL}" 

e recebo a seguinte sugestão

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

Do ponto de vista prático não vejo diferença

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

Já que basenameestá noEspecificações POSIX, não sei por que deveria ser uma prática recomendada. Alguma dica?

Responder1

Não se trata de eficiência – trata-se de correção. basenameusa novas linhas para delimitar os nomes dos arquivos que imprime. No caso normal, quando você passa apenas um nome de arquivo, ele adiciona uma nova linha à sua saída. Como os nomes dos arquivos podem conter novas linhas, isso torna difícil lidar corretamente com esses nomes de arquivos.

É ainda mais complicado pelo fato de que as pessoas costumam usar basenameassim: "$(basename "$file")". Isto torna as coisas ainda mais difíceis, porque $(command)as tirastodosnovas linhas à direita de command. Considere o caso improvável que $filetermina com uma nova linha. Em seguida, basenameadicionará uma nova linha extra, mas "$(basename "$file")"removeráambosnovas linhas, deixando você com um nome de arquivo incorreto.

Outro problema basenameé que se $filecomeçar com a -(traço ou menos), será interpretado como uma opção. Este é fácil de corrigir:$(basename -- "$file")

A maneira robusta de usar basenameé esta:

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

Uma alternativa é usar ${file##*/}, que é mais fácil, mas possui seus próprios bugs. Em particular, está errado nos casos em que $fileis /ou foo/.

Responder2

As linhas relevantes em shellcheck'sCódigo fontesão:

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

Não há nenhuma explicação dada explicitamente, mas com base no nome da função ( checkNeedlessCommands), parece que @jordanm está certo e sugere que você evite bifurcar um novo processo.

Responder3

dirname, basename, readlinketc (obrigado @Marco - isso foi corrigido) podem criar problemas de portabilidade quando a segurança se torna importante (exigindo segurança do caminho). Muitos sistemas (como o Fedora Linux) colocam-no em /binenquanto outros (como o Mac OSX) colocam-no em /usr/bin. Depois, há o Bash no Windows, por exemplo, cygwin, msys e outros. É sempre melhor permanecer puro Bash, quando possível.(por comentário do @Marco)

A propósito, obrigado pela indicação do shellcheck, nunca vi isso antes.

informação relacionada