Y a-t-il une raison objective de préférer une forme à l'autre? Performance, fiabilité, portabilité?
filename=/some/long/path/to/a_file
parentdir_v1="${filename%/*}"
parentdir_v2="$(dirname "$filename")"
basename_v1="${filename##*/}"
basename_v2="$(basename "$filename")"
echo "$parentdir_v1"
echo "$parentdir_v2"
echo "$basename_v1"
echo "$basename_v2"
Produit:
/some/long/path/to
/some/long/path/to
a_file
a_file
(V1 utilise l'expansion du paramètre Shell, V2 utilise des binaires externes.)
Tous deux ont leurs bizarreries, malheureusement.
Les deux sont requis par POSIX, la différence entre eux n'est pas une souci de portabilité¹.
Le moyen clair d'utiliser les utilitaires est
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Notez les citations doubles autour des substitutions variables, comme toujours, ainsi que le --
Après la commande, au cas où le nom du fichier commence par un tiret (sinon, les commandes interprètent le nom du fichier en option). Cela échoue toujours dans un boîtier de bord, qui est rare mais pourrait être forcé par un utilisateur malveillant²: la substitution de commande supprime les nouvelles lignes suivantes. Donc, si un nom de fichier est appelé foo/bar
alors base
_ sera défini sur bar
au lieu de bar
. Une solution de contournement consiste à ajouter un personnage non nouvelle ligne et à le dépouiller 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 courez pas dans des cas de bord liés à l'expansion des personnages étranges, mais il existe un certain nombre de difficultés avec le caractère SLASH. Une chose qui n'est pas un cas d'avance du tout est que le calcul de la partie répertoire nécessite un code différent pour le cas où il n'y a pas /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
Le boîtier de bord est quand il y a une barre oblique de fin (y compris le cas du répertoire racine, qui est toutes des barres obliques). Les commandes basename
et dirname
Dettez les barres obliques avant de faire leur travail. Il n'y a aucun moyen de dissimuler les barres obliques en une fois si vous vous en tenez à des constructions de POSIX, mais vous pouvez le faire en deux étapes. Vous devez prendre soin de l'affaire lorsque l'entrée consiste à ne rien faire d'autre que des 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 d'Edge (par exemple, un résultat find
, autre que le point de départ contient toujours une partie de répertoire et n'a pas de fuite /
) Alors la manipulation de la chaîne d'expansion des paramètres est simple. Si vous devez faire face à tous les cas de bord, les utilitaires sont plus faciles à utiliser (mais plus lentement).
Parfois, vous voudrez peut-être traiter foo/
Comme foo/.
plutôt que comme foo
. Si vous agissez sur une entrée d'annuaire, alors foo/
est censé être équivalent à foo/.
, pas foo
; Cela fait une différence lorsque foo
est un lien symbolique vers un répertoire: foo
désigne la liaison symbolique, foo/
désigne le répertoire cible. Dans ce cas, le nom d'un chemin avec une barre oblique est avantageusement .
, et le chemin peut être son propre dirname.
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 (ces premières bandes traînantes slashes, comme les utilitaires):
dir=$filename:h base=$filename:t
¹ Sauf si vous utilisez des coquillages pré-posix comme Solaris 10 et Animé's /bin/sh
(il manquait des caractéristiques de manipulation de chaîne de groupe de paramètres sur des machines toujours en production - mais il y a toujours une coquille POSIX appelée sh
dans l'installation, seulement c'est /usr/xpg4/bin/sh
, ne pas /bin/sh
).
[.____] ² Par exemple: soumettre un fichier appelé foo
à un service de téléchargement de fichier qui ne protège pas contre cela, puis supprimez-le et cause foo
à supprimer à la place.
Les deux sont à Posix, la portabilité "devrait donc" ne pas être préoccupante. Les substitutions de la coque doivent être présumées pour courir plus vite.
Cependant, cela dépend de ce que vous entendez par courrier portable. Certains systèmes anciens (pas nécessairements) n'ont pas mis en œuvre ces caractéristiques dans leur /bin/sh
(Solaris 10 ans et plus viennent à l'esprit), tandis qu'elles sont en revanche, un certain temps, les développeurs ont été avertis que dirname
n'était pas aussi portable que basename
.
Pour référence:
DirName - renvoie la partie de répertoire d'un chemin de chemin (POSIX)
L'utilitaire dirname est originaire du système III. Il a évolué via les versions du système V à une version correspondant aux exigences spécifiées dans cette description dans la version System V 3. 4.3 Les versions BSD et antérieures n'incluaient pas le dirname.
page manuelle sur Solaris 1 (Oracle)
[.____] La page manuelle ne mentionne pas ##
ou %/
.
En considérant la portabilité, je devrais prendre en compte tout des systèmes où je maintiens les programmes. Tous ne sont pas POSIX, il y a donc des compromis. Vos compromis peuvent différer.
Il y a aussi:
mkdir '
'; dir=$(basename ./'
'); echo "${#dir}"
0
Des trucs étranges comme cela se produisent car il y a beaucoup d'interprétation et d'analyse et le reste qui doit se produire lorsque deux processus parlent. Les substitutions de commandement dépasseront les nouvelles lignes suivantes. Et nuls (si cela n'est évidemment pas pertinent ici). basename
et dirname
éliminera également les nouvelles lignes suivantes, car alors comment y parlez-vous? Je sais que les nouvelles lignes suivantes dans un nom de fichier sont une sorte d'anathème de toute façon, mais vous ne savez jamais. Et il n'a pas de sens d'aller à la manière éventuellement imparfaite lorsque vous pouviez faire autrement.
Toujours... ${pathname##*/} != basename
et également ${pathname%/*} != dirname
. Ces commandes sont spécifiées pour effectuer un surtout Séquence bien définie d'étapes pour obtenir leurs résultats spécifiés.
La spécification est ci-dessous, mais d'abord voici une version erser:
basename()
case $1 in
(*[!/]*/) basename "${1%"${1##*[!/]}"}" ${2+"$2"} ;;
(*/[!/]*) basename "${1##*/}" ${2+"$2"} ;;
(${2:+?*}"$2") printf %s%b\\n "${1%"$2"}" "${1:+\n\c}." ;;
(*) printf %s%c\\n "${1##///*}" "${1#${1#///}}" ;;
esac
C'est une conformité complet de POSIX basename
en simple sh
. 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:
basename()
case $1 in
("") # 1. If string is a null string, it is
# unspecified whether the resulting string
# is '.' or a null string. In either case,
# skip steps 2 through 6.
echo .
;; # I feel like I should flip a coin or something.
(//) # 2. If string is "//", it is implementation-
# defined whether steps 3 to 6 are skipped or
# or processed.
# Great. What should I do then?
echo //
;; # I guess it's *my* implementation after all.
(*[!/]*/) # 3. If string consists entirely of <slash>
# characters, string shall be set to a sin‐
# gle <slash> character. In this case, skip
# steps 4 to 6.
# 4. If there are any trailing <slash> characters
# in string, they shall be removed.
basename "${1%"${1##*[!/]}"}" ${2+"$2"}
;; # Fair enough, I guess.
(*/) echo /
;; # For step three.
(*/*) # 5. If there are any <slash> characters remaining
# in string, the prefix of string up to and
# including the last <slash> character in
# string shall be removed.
basename "${1##*/}" ${2+"$2"}
;; # == ${pathname##*/}
("$2"|\
"${1%"$2"}") # 6. If the suffix operand is present, is not
# identical to the characters remaining
# in string, and is identical to a suffix of
# the characters remaining in string, the
# the suffix suffix shall be removed from
# string. Otherwise, string is not modi‐
# fied by this step. It shall not be
# considered an error if suffix is not
# found in string.
printf %s\\n "$1"
;; # So far so good for parameter substitution.
(*) printf %s\\n "${1%"$2"}"
esac # I probably won't do dirname.
... Peut-être que les commentaires distraient ....
Vous pouvez obtenir un coup de pouce à partir du processus basename
et dirname
(Je ne comprends pas pourquoi ceux-ci ne sont pas construits - si ceux-ci ne sont pas candidats, je ne sais pas ce qui est ) Mais la mise en œuvre doit gérer des choses comme:
path dirname basename
"/usr/lib" "/usr" "lib"
"/usr/" "/" "usr"
"usr" "." "usr"
"/" "/" "/"
"." "." "."
".." "." ".."
^ Du BasEname (3)
et autres cas de bord.
J'utilise:
basename(){
test -n "$1" || return 0
local x="$1"; while :; do case "$x" in */) x="${x%?}";; *) break;; esac; done
[ -n "$x" ] || { echo /; return; }
printf '%s\n' "${x##*/}";
}
dirname(){
test -n "$1" || return 0
local x="$1"; while :; do case "$x" in */) x="${x%?}";; *) break;; esac; done
[ -n "$x" ] || { echo /; return; }
set -- "$x"; x="${1%/*}"
case "$x" in "$1") x=.;; "") x=/;; esac
printf '%s\n' "$x"
}
(Ma dernière implémentation de =GNU basename
et dirname
ajoute quelques commutateurs de ligne de commande de fantaisie spéciaux pour la manipulation de plusieurs arguments ou suffixe, mais c'est super facile ajouter dans la coquille.)
Il n'est pas difficile de les rendre dans bash
_ comités (en utilisant la mise en œuvre du système sous-jacent), mais la fonction ci-dessus ne doit pas nécessairement être compilée et fournit également un coup de pouce également.