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.