shellcheck conseille de ne pas utiliser le nom de base: pourquoi?

25

J'essaie le shellcheck .

J'ai quelque chose comme ça

basename "${OPENSSL}" 

et je reçois la suggestion suivante

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

D'un point de vue pratique, je ne vois aucune différence

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

Puisque basenamec'est dans les spécifications POSIX , je n'ai aucune raison pour que ce soit la meilleure pratique. Un indice?

Matteo
la source
8
Il lance un nouveau processus lorsqu'il n'en a pas besoin.
jordanm
@jordanm assez juste ... Je ne pensais pas à l'efficacité.
Matteo
3
@jordanm D'autre part, il fonctionne avec des shells autres que bash
Matteo
1
@JosephR. c'est ce que je pensais, mais je viens de découvrir que cela ne fonctionne pas csh. Je suppose que ce cshn'est pas POSIX alors.
terdon
3
@terdon - csh est très loin de POSIX.
jordanm

Réponses:

25

Ce n'est pas une question d'efficacité - c'est une question d'exactitude. basenameutilise des retours à la ligne pour délimiter les noms de fichiers imprimés. Dans le cas habituel, lorsque vous ne passez qu'un seul nom de fichier, il ajoute une nouvelle ligne de fin à sa sortie. Étant donné que les noms de fichiers peuvent contenir eux-mêmes des sauts de ligne, il est difficile de gérer correctement ces noms de fichiers.

Il est encore compliqué par le fait que les gens utilisent habituellement basenamecomme ceci: "$(basename "$file")". Cela rend les choses encore plus difficiles, car elles $(command)suppriment toutes les nouvelles lignes de fin command. Considérez le cas peu probable qui $filese termine par une nouvelle ligne. Ensuite , basenameajoutera une nouvelle ligne supplémentaire, mais "$(basename "$file")"dépouillera les deux nouvelles lignes, vous laissant avec un nom de fichier incorrect.

Un autre problème avec basenameest que si $filecommence par un -(tiret aka moins), il sera interprété comme une option. Celui-ci est facile à réparer:$(basename -- "$file")

La manière robuste d'utiliser basenameest la suivante:

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

Une alternative est d'utiliser ${file##*/}, qui est plus facile mais qui a ses propres bugs. En particulier, c'est faux dans les cas où $fileest /ou foo/.

Mat
la source
2
Très bons points, +1. Heureux que le PO ait accepté cela au lieu du mien.
terdon
2
Contrepoint: si $fileest foo/? Et si c'est le cas /?
Gilles 'SO- arrête d'être méchant'
2
@ Gilles: Tu as raison. Je commence à penser que l' basenameapproche est meilleure après tout, aussi hacky soit-elle. Les meilleures alternatives que je peux trouver sont ${${${file}%%/#}##*/}and [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1], qui sont toutes les deux spécifiques à zsh et qui ne gèrent pas /correctement.
Matt
@terdon Merci de ne pas l'avoir pris personnellement :-). Les fichiers avec des sauts de ligne ne sont pas courants mais Matt a raison. Bien sûr, l'utilisation de la substitution de variables est également plus efficace.
Matteo
16

Les lignes concernées shellcheckdu » code source sont:

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

Il n'y a aucune explication donnée explicitement mais basée sur le nom de la fonction ( checkNeedlessCommands) il semble que @jordanm soit tout à fait raison et cela vous suggère d'éviter de bifurquer un nouveau processus.

terdon
la source
7
Que la source soit avec vous :)
Joseph R.
3

dirname, basename, readlinkEtc (merci @Marco - ceci est corrigé) peut créer des problèmes de portabilité lorsque la sécurité est importante (nécessitant la sécurité du chemin). De nombreux systèmes (comme Fedora Linux) le placent sur /bintandis que d'autres (comme Mac OSX) le placent sur /usr/bin. Ensuite, il y a Bash sur Windows, par exemple cygwin, msys et autres. Il est toujours préférable de rester pur Bash, lorsque cela est possible. (par commentaire @Marco)

BTW, merci pour le pointeur sur shellcheck, je n'ai jamais vu ça avant.

AsymLabs
la source
1
1) Qu'entendez-vous par «sécurité»? Peux-tu élaborer? 2) Le PO ne le mentionne pas dirnamedu tout. 3) Les utilitaires de base devraient être dans le CHEMIN, où qu'ils soient stockés. Je n'ai pas encore vu un système qui basenamen'était pas dans le CHEMIN. 4) En supposant que bash est disponible est un problème de portabilité. Il est toujours préférable de rester loin de bash et d'utiliser un shell POSIX lorsque la portabilité est requise.
Marco
@Marco Merci d'avoir signalé ces problèmes. Oui, vous avez raison, les utilitaires sont sur le chemin. Mais là où l'on veut apporter une sécurité supplémentaire à un script Bash, il est recommandé de fournir le chemin absolu. Donc, appeler '/ bin / basename' fonctionnera pour les systèmes RedHat, mais cela produira une erreur sur un Mac. Ceci est mieux expliqué dans le Bash Cookbook où environ un quart des 600 pages est consacré à ce sujet. Nous avons fait une tentative approximative pour résoudre les problèmes de sécurité dans notre source libre secure-lib .
AsymLabs
@Marco Point 4 est un commentaire valide mais la question (OP) commence par et est écrite autour de shellcheck qui est conçu pour vérifier les deux scripts sh / bash. Par conséquent, nous devons supposer que ce n'est pas strictement Posix par défaut.
AsymLabs
@Marco La sécurité de la variable d'environnement de chemin peut être compromise facilement. Par exemple, j'ai été très surpris de découvrir il y a quelques années que le Ruby RVM, installé localement, plaçait par défaut son chemin avant le chemin du système.
AsymLabs
L'OP mentionne spécifiquement POSIX et la question est balisée posixet non bash. Je ne trouve aucun indicateur que l'OP nécessite une solution bash. Votre déclaration "Il vaut toujours mieux rester pur Bash" est tout simplement fausse, je suis désolé.
Marco