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/baril basesera défini sur au barlieu 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 basenameet dirnamesuppriment 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 findré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 fooest un lien symbolique vers un répertoire: foosignifie 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é shdans 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- foole 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.