shellcheck está avisando para não usar o basename: why?

25

Estou experimentando verificação de shell .

Eu tenho algo parecido com isso

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 basename está nas especificações POSIX , não entendo por que deve ser a melhor prática. Alguma dica?

    
por Matteo 09.10.2013 / 18:07

3 respostas

24

Não se trata de eficiência, é sobre correção. basename usa novas linhas para delimitar os nomes dos 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 manuseio correto desses nomes de arquivos.

É ainda mais complicado pelo fato de as pessoas geralmente usarem basename assim: "$(basename "$file") ". Isso dificulta ainda mais as coisas, porque $(command) retira todas as últimas linhas de command Considere o caso improvável de que $file termine com uma nova linha. Então, basename adicionará uma nova linha extra, mas "$(basename "$file")" removerá ambas novas linhas, deixando você com um nome de arquivo incorreto.

Outro problema com basename é que, se $file começar com - (traço a.k.a. 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##*/} , que é mais fácil, mas tem erros próprios. Em particular, está errado nos casos em que $file é / ou foo/ .

    
por 09.10.2013 / 19:59
16

As linhas relevantes no código-fonte do shellcheck 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 o @jordanm está correto e está sugerindo que você evite um novo processo.

    
por 09.10.2013 / 18:20
3

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

BTW, obrigado pelo ponteiro para a shellcheck, eu não vi isso antes.

    
por 09.10.2013 / 20:55