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

25

Estou testando o shellcheck .

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

Como basenameestá nas especificações do POSIX , não faço uma razão para que seja uma prática recomendada. Alguma dica?

Matteo
fonte
8
Ele bifurca um novo processo quando não precisa.
Jordanm #
@ Jordanm é justo o suficiente ... Eu não pensei em eficiência.
Matteo
3
@jordanm Por outro lado, trabalha com em cascas de outro do que o bash
Matteo
11
@JosephR. foi o que pensei, mas descobri que não funciona csh. Eu acho que cshnão é POSIX então.
terdon
3
@terdon - csh está muito longe do POSIX.
Jordanm #

Respostas:

25

Não se trata de eficiência - é de correção. basenameusa novas linhas para delimitar os nomes de arquivos impressos. No caso usual, quando você passa apenas um nome de arquivo, ele adiciona uma nova linha à sua saída. Como os nomes de arquivos podem conter novas linhas, isso dificulta o tratamento correto desses nomes de arquivo.

É ainda mais complicada pelo fato de que as pessoas geralmente usam basenamecomo este: "$(basename "$file")". Isso torna as coisas ainda mais difíceis, porque $(command)retira todas as novas linhas finais command. Considere o caso improvável que $filetermina com uma nova linha. Em seguida, basenameirá adicionar uma nova linha extra, mas "$(basename "$file")"irá retirar as duas novas linhas, deixando-o com um nome de arquivo incorreto.

Outro problema basenameé que, se $filecomeçar com um -(traço aka menos), ele 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##*/}, o que é mais fácil, mas possui bugs próprios. Em particular, está errado nos casos em que $fileé /ou foo/.

Matt
fonte
2
Muito bons pontos, +1. Que bom que o OP aceitou isso em vez do meu.
terdon
2
Contraponto: e se $filefor foo/? E se for /?
Gilles 'SO- stop be evil'
2
@Gilles: Você está certo. Estou começando a pensar que a basenameabordagem é melhor, afinal, por mais hacky que seja. As melhores alternativas que posso encontrar são ${${${file}%%/#}##*/}e [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1], sendo ambas específicas do zsh e nenhuma das quais é manipulada /corretamente.
Matt
@terdon Obrigado por não ter levado para o lado pessoal :-). Arquivos com novas linhas não são comuns, mas Matt tem razão. É claro que o uso de substituição de variáveis ​​também é mais eficiente.
Matteo
16

As linhas relevantes shellcheckdo código fonte sã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á 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.

terdon
fonte
7
Pode a fonte ser com você :)
Joseph R.
3

dirname, basename, readlinkEtc (graças @Marco - este é corrigida) pode criar problemas de portabilidade quando a segurança torna-se importante (o que exige a segurança do caminho). Muitos sistemas (como o Fedora Linux) o colocam, /binenquanto outros (como o Mac OSX) o colocam /usr/bin. Depois, há o Bash no Windows, por exemplo, cygwin, msys e outros. É sempre melhor manter o Bash puro, quando possível. (por comentário do @Marco)

BTW, obrigado pelo ponteiro para verificar, eu nunca vi isso antes.

AsymLabs
fonte
11
1) O que você quer dizer com "segurança"? Você pode elaborar? 2) O PO não menciona dirnamenada. 3) Os utilitários básicos básicos devem estar no PATH, onde quer que estejam armazenados. Ainda não vi um sistema que basenamenão estava no PATH. 4) Assumir que o bash está disponível é uma questão de portabilidade. É sempre melhor ficar longe do bash e usar um shell POSIX quando a portabilidade é necessária.
Marco
@Marco Obrigado por apontar essas questões. Sim, você está certo de que os utilitários estão no caminho. Mas onde se deseja fornecer segurança adicional a um script Bash, é uma boa prática fornecer o caminho absoluto. Assim, chamar '/ bin / basename' funcionará nos sistemas RedHat, mas produzirá um erro no Mac. Isso é melhor explicado no Bash Cookbook, onde cerca de um quarto das 600 páginas é dedicado a esse assunto. Fizemos uma tentativa grosseira de resolver os problemas de segurança existentes em nossa fonte segura-lib gratuita .
AsymLabs
O @Marco Point 4 é um comentário válido, mas a pergunta (OP) começa com e é escrita em torno do shellcheck, que é projetado para verificar os dois scripts sh / bash. Portanto, temos que assumir que não é estritamente Posix por padrão.
AsymLabs
@Marco A segurança da variável de ambiente do caminho pode ser facilmente comprometida. Por exemplo, fiquei muito surpreso ao descobrir alguns anos atrás que o Ruby RVM, instalado localmente, por padrão, colocava seu caminho antes do caminho do sistema.
AsymLabs 10/10
O OP menciona especificamente POSIX e a pergunta está marcada posixe não bash. Não consigo encontrar nenhum indicador de que o OP exija uma solução bash. Sua afirmação "É sempre melhor permanecer pura Bash" está completamente errada, me desculpe.
Marco