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.foo/
comofoo
, no comofoo/.
, lo que no es consistente con las utilidades compatibles con POSIX./
si lo necesito.find
resultado, que siempre contiene una parte del directorio y no tiene seguimiento/
" No es del todo cierto, sefind ./
mostrará./
como primer resultado.Ambos están en POSIX, por lo que la portabilidad "debería" no ser motivo de preocupación. Se debe suponer que las sustituciones de shell se ejecutan más rápido.
Sin embargo, depende de lo que entiendas por portátil. Algunos sistemas antiguos (no necesariamente) no implementaron esas características en su
/bin/sh
(Solaris 10 y versiones anteriores vienen a la mente), mientras que, por otro lado, hace un tiempo, se advirtió a los desarrolladores quedirname
no era tan portátil comobasename
.Para referencia:
dirname: devuelve la parte del directorio de un nombre de ruta (POSIX)
sh página de manual en Solaris 10 (Oracle)
La página de manual no menciona
##
ni%/
.Al considerar la portabilidad, tendría que tener en cuenta todos los sistemas donde mantengo programas. No todos son POSIX, por lo que hay compensaciones. Sus compensaciones pueden diferir.
fuente
También hay:
Suceden cosas extrañas como esa porque hay mucha interpretación y análisis y el resto que debe suceder cuando dos procesos hablan. Las sustituciones de comandos eliminarán las nuevas líneas finales. Y NUL (aunque eso obviamente no es relevante aquí) .
basename
ydirname
también eliminará las nuevas líneas finales en cualquier caso porque, ¿de qué otra manera les hablas? Lo sé, las nuevas líneas finales en un nombre de archivo son una especie de anatema de todos modos, pero nunca se sabe. Y no tiene sentido seguir el camino posiblemente defectuoso cuando podría hacerlo de otra manera.Aún así ...
${pathname##*/} != basename
y de la misma manera${pathname%/*} != dirname
. Esos comandos se especifican para llevar a cabo una secuencia de pasos mayormente bien definida para llegar a los resultados especificados.La especificación está debajo, pero primero aquí hay una versión terser:
Eso es totalmente compatible con POSIX
basename
en simplesh
. No es difícil de hacer. Fusioné un par de ramas que uso a continuación porque pude sin afectar los resultados.Aquí está la especificación:
... tal vez los comentarios distraen ...
fuente
[!/]
antes, ¿es así[^/]
? Pero su comentario al lado de eso no parece coincidir ...basename
es un conjunto de instrucciones sobre cómo hacerlo con su shell. Pero[!charclass]
es la forma portátil de hacer eso con globs[^class]
es para expresiones regulares, y los proyectiles no están especificados para expresiones regulares. Sobre el juego del comentario ...case
filtros, por lo que si coinciden con una cadena que contiene una barra diagonal/
y un!/
entonces si el siguiente patrón caso por debajo de partidos cualquier arrastran/
golpes rápidos en todos ellos sólo pueden ser todas las barras. Y uno más abajo que no puede tener ningún seguimiento /Puede obtener un impulso en el proceso
basename
ydirname
(no entiendo por qué estos no son incorporados, si no son candidatos, no sé qué es), pero la implementación debe manejar cosas como:^ De basename (3)
y otros casos de borde.
He estado usando:
(Mi última implementación de GNU
basename
ydirname
agrega algunos interruptores especiales de línea de comandos sofisticados para cosas como el manejo de múltiples argumentos o la eliminación de sufijos, pero eso es muy fácil de agregar en el shell).No es tan difícil convertirlos en
bash
componentes incorporados (haciendo uso de la implementación del sistema subyacente), pero la función anterior no necesita compilarse, y también brindan cierto impulso.fuente
x//
correctamente, pero lo solucioné antes de responder. Espero que sea eso.dirname a///b//c//d////e
rendimientosa///b//c//d///
.