Les deux ont leurs bizarreries, malheureusement.
Les deux sont requis par POSIX, donc la différence entre eux n'est pas un problème de portabilité¹.
La manière simple d'utiliser les utilitaires est
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Notez les guillemets doubles autour des substitutions de variables, comme toujours, et également --
après la commande, au cas où le nom de fichier commence par un tiret (sinon les commandes interpréteraient le nom de fichier comme une option). Cela échoue toujours dans un cas de bord, ce qui est rare mais peut être forcé par un utilisateur malveillant²: la substitution de commande supprime les nouvelles lignes de fin. Donc, si un nom de fichier est appelé, foo/bar
il base
sera défini sur au bar
lieu de bar
. Une solution de contournement consiste à ajouter un caractère non-retour à la ligne et à le supprimer après la substitution de commande:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Avec la substitution de paramètres, vous ne rencontrez pas de cas marginaux liés à l'expansion de caractères étranges, mais il y a un certain nombre de difficultés avec le caractère barre oblique. Une chose qui n'est pas du tout un cas de bord est que le calcul de la partie répertoire nécessite un code différent pour le cas où il n'y en a pas /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Le cas de bord est quand il y a une barre oblique de fin (y compris le cas du répertoire racine, qui est toutes les barres obliques). Les commandes basename
et dirname
suppriment les barres obliques avant de faire leur travail. Il n'y a aucun moyen de supprimer les barres obliques en une seule fois si vous vous en tenez aux constructions POSIX, mais vous pouvez le faire en deux étapes. Vous devez prendre soin de l'affaire lorsque l'entrée ne se compose que de barres obliques.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Si vous savez que vous n'êtes pas dans un cas de bord (par exemple, un find
résultat autre que le point de départ contient toujours une partie de répertoire et n'a pas de fin /
), la manipulation de la chaîne d'extension des paramètres est simple. Si vous devez gérer tous les cas de bord, les utilitaires sont plus faciles à utiliser (mais plus lents).
Parfois, vous voudrez peut-être traiter foo/
comme foo/.
plutôt que comme foo
. Si vous agissez sur une entrée de répertoire, foo/
est censé être équivalent à foo/.
, non foo
; cela fait une différence quand foo
est un lien symbolique vers un répertoire: foo
signifie le lien symbolique, foo/
signifie le répertoire cible. Dans ce cas, le nom de base d'un chemin d'accès avec une barre oblique de fin est avantageusement .
, et le chemin d'accès peut être son propre nom de répertoire.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
La méthode rapide et fiable consiste à utiliser zsh avec ses modificateurs d'historique (ce premier supprime les barres obliques de fin, comme les utilitaires):
dir=$filename:h base=$filename:t
¹ À moins que vous n'utilisiez des shells pré-POSIX comme Solaris 10 et les versions antérieures /bin/sh
(qui manquaient de fonctionnalités de manipulation de chaîne d'extension des paramètres sur les machines encore en production - mais il y a toujours un shell POSIX appelé sh
dans l'installation, seulement c'est /usr/xpg4/bin/sh
, non /bin/sh
).
² Par exemple: soumettre un fichier appelé foo
à un service de téléchargement de fichiers qui ne protège pas contre cela, puis supprimez-le et faites- foo
le supprimer à la place
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Je lisais attentivement et je n'ai pas remarqué que vous mentionniez des inconvénients.foo/
commefoo
, pas commefoo/.
, ce qui n'est pas compatible avec les utilitaires compatibles POSIX./
si je en ai besoin.find
résultat, qui contient toujours une partie de répertoire et qui n'a pas de fin/
" Pas tout à fait vrai,find ./
sera affiché./
comme premier résultat.Les deux sont en POSIX, donc la portabilité "ne devrait" pas être un problème. Les substitutions de shell doivent être supposées s'exécuter plus rapidement.
Cependant - cela dépend de ce que vous entendez par portable. Certains anciens systèmes (pas nécessairement) n'ont pas implémenté ces fonctionnalités dans leur
/bin/sh
(Solaris 10 et plus ancien me viennent à l'esprit), tandis que d'un autre côté, les développeurs ont été avertis que cedirname
n'était pas aussi portable quebasename
.Pour référence:
dirname - retourne la partie répertoire d'un chemin (POSIX)
sh page de manuel sur Solaris 10 (Oracle)
La page de manuel ne mentionne pas
##
ou%/
.En considérant la portabilité, je devrais prendre en compte tous les systèmes où je maintiens des programmes. Tous ne sont pas POSIX, il y a donc des compromis. Vos compromis peuvent différer.
la source
Il y a aussi:
Des choses étranges comme ça se produisent parce qu'il y a beaucoup d'interprétation et d'analyse et le reste qui doit se produire lorsque deux processus parlent. Les substitutions de commandes suppriment les nouvelles lignes de fin. Et NULs (bien que ce ne soit évidemment pas pertinent ici) .
basename
etdirname
supprimera également les nouvelles lignes en fin de cas, car comment leur parlez-vous autrement? Je sais, les sauts de ligne dans un nom de fichier sont en quelque sorte un anathème, mais on ne sait jamais. Et cela n'a pas de sens d'emprunter la voie éventuellement défectueuse alors que vous pourriez faire autrement.Toujours ...
${pathname##*/} != basename
et de même${pathname%/*} != dirname
. Ces commandes sont spécifiées pour effectuer une séquence d'étapes principalement bien définie pour arriver à leurs résultats spécifiés.La spécification est ci-dessous, mais voici d'abord une version terser:
C'est entièrement compatible POSIX
basename
simplesh
. Ce n'est pas difficile à faire. J'ai fusionné quelques branches que j'utilise ci-dessous car je pouvais sans affecter les résultats.Voici la spécification:
... peut-être que les commentaires distraient ...
la source
[!/]
auparavant, c'est comme ça[^/]
? Mais votre commentaire à côté de cela ne semble pas correspondre ...basename
est un ensemble d'instructions sur la façon de le faire avec votre shell. Mais[!charclass]
la façon portable de le faire avec des globes[^class]
est pour regex - et les shells ne sont pas spécifiés pour regex. À propos descase
filtres correspondant au commentaire ... , donc si je fais correspondre une chaîne qui contient une barre oblique de fin/
et un!/
si le motif de cas suivant ci-dessous correspond à des/
barres obliques de fin, il ne peut s'agir que de toutes les barres obliques. Et celui ci-dessous qui ne peut pas avoir de fuite /Vous pouvez obtenir un coup de pouce du processus en cours
basename
etdirname
(je ne comprends pas pourquoi ceux-ci ne sont pas intégrés - si ce ne sont pas des candidats, je ne sais pas ce que c'est) mais l'implémentation doit gérer des choses comme:^ Du nom de base (3)
et d'autres cas de bord.
J'utilise:
(Ma dernière implémentation de GNU
basename
etdirname
ajoute quelques commutateurs de ligne de commande fantaisie spéciaux pour des choses telles que la gestion de plusieurs arguments ou la suppression des suffixes, mais c'est super facile à ajouter dans le shell.)Ce n'est pas si difficile de les intégrer dans les
bash
builds non plus (en utilisant l'implémentation du système sous-jacent), mais la fonction ci-dessus n'a pas besoin d'être compilée, et elle fournit également un coup de pouce.la source
x//
correctement les choses , mais j'ai corrigé pour vous avant de répondre. J'espère que c'est ça.dirname a///b//c//d////e
rendementsa///b//c//d///
.