Ambos tienen sus peculiaridades, desafortunadamente.
POSIX requiere ambos, por lo que la diferencia entre ellos no es un problema de portabilidad¹.
La manera simple de usar las utilidades es
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
Tenga en cuenta las comillas dobles alrededor de las sustituciones de variables, como siempre, y también --
después del comando, en caso de que el nombre del archivo comience con un guión (de lo contrario, los comandos interpretarían el nombre del archivo como una opción). Esto todavía falla en un caso extremo, lo cual es raro pero puede ser forzado por un usuario malintencionado²: la sustitución de comandos elimina las nuevas líneas finales. Así que si un nombre de archivo se denomina foo/bar
a continuación base
se establecerá en bar
lugar de bar
. Una solución alternativa es agregar un carácter que no sea de nueva línea y eliminarlo después de la sustitución del comando:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
Con la sustitución de parámetros, no se topa con casos extremos relacionados con la expansión de caracteres extraños, pero hay una serie de dificultades con el carácter de barra diagonal. Una cosa que no es un caso límite en absoluto es que calcular la parte del directorio requiere un código diferente para el caso donde no hay /
.
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
El caso límite es cuando hay una barra inclinada final (incluido el caso del directorio raíz, que es todo una barra inclinada). Los comandos basename
y dirname
eliminan las barras diagonales antes de hacer su trabajo. No hay forma de quitar las barras diagonales de una vez si se adhiere a las construcciones POSIX, pero puede hacerlo en dos pasos. Debe ocuparse del caso cuando la entrada consiste en nada más que barras.
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
Si sabe que no está en un caso límite (p. Ej., Un find
resultado que no sea el punto de partida siempre contiene una parte del directorio y no tiene seguimiento /
), entonces la manipulación de la cadena de expansión de parámetros es sencilla. Si necesita hacer frente a todos los casos límite, las utilidades son más fáciles de usar (pero más lentas).
A veces, es posible que desee tratar foo/
como en foo/.
lugar de como foo
. Si está actuando en una entrada de directorio, foo/
se supone que es equivalente a foo/.
, no foo
; esto hace la diferencia cuando foo
hay un enlace simbólico a un directorio: foo
significa el enlace simbólico, foo/
significa el directorio de destino. En ese caso, el nombre base de una ruta con una barra diagonal final es ventajosa .
, y la ruta puede ser su propio nombre de directorio.
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
El método rápido y confiable es usar zsh con sus modificadores de historial (este primero elimina las barras diagonales finales, como las utilidades):
dir=$filename:h base=$filename:t
¹ A menos que esté utilizando shells pre-POSIX como Solaris 10 y anteriores /bin/sh
(que carecían de características de manipulación de cadenas de expansión de parámetros en máquinas que aún se encontraban en producción, pero siempre hay un shell POSIX llamado sh
en la instalación, solo que /usr/xpg4/bin/sh
no /bin/sh
).
² Por ejemplo: envíe un archivo llamado foo
a un servicio de carga de archivos que no protege contra esto, luego elimínelo y haga foo
que se elimine en su lugar
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? Estaba leyendo detenidamente y no noté que mencionaras ningún inconveniente.