¿Cómo encontrar archivos en subdirecciones y ordenarlos por nombre de archivo en un solo comando?

9

Resultado de un hallazgo normal usando find . ! -path "./build*" -name "*.txt":

./tool/001-sub.txt
./tool/000-main.txt
./zo/001-int.txt
./zo/id/002-and.txt
./as/002-mod.txt

y cuando se ordena con sort -n:

./as/002-mod.txt
./tool/000-main.txt
./tool/001-sub.txt
./zo/001-int.txt
./zo/id/002-and.txt

sin embargo, la salida deseada es:

./tool/000-main.txt
./zo/001-int.txt
./tool/001-sub.txt
./zo/id/002-and.txt
./as/002-mod.txt

lo que significa que la salida se ordena solo en nombre de archivo , pero la información de la carpeta debe mantenerse como parte de la salida.

Editar : Haga que el ejemplo sea más complicado ya que la estructura del subdirectorio puede incluir más de un nivel.

unode
fuente
2
Vea esta pregunta que hice en SO: stackoverflow.com/questions/3222810/…
camh
@camh: si es posible, me gustaría usar solo comandos Unix. En cualquier caso, mi pregunta es prácticamente un duplicado de la suya. ¿Puede transferir la mejor solución a este hilo (mantenga un enlace al original de todos modos) para que pueda marcar como la solución?
Unode
Si @Shawn realiza los cambios que sugerí en mi comentario (usar en -printflugar de awk), creo que esa es la mejor solución. He revisado mi implementación original para usar este método.
camh

Respuestas:

9

Debe ordenar por el último campo (considerando /como un separador de campo). Desafortunadamente, no puedo pensar en una herramienta que pueda hacer esto cuando la cantidad de campos varía (si solo sort -kpudiera tomar valores negativos).

Para evitar esto, tendrás que hacer una decoración-ordenar-decorar. Es decir, tome el nombre del archivo y póngalo al principio seguido de un separador de campo, luego haga una ordenación, luego elimine la primera columna y el separador de campo.

find . ! -path "./build*" -name "*.txt" |\
    awk -vFS=/ -vOFS=/ '{ print $NF,$0 }' |\
    sort -n -t / |\
    cut -f2- -d/

Ese awkcomando dice que el separador de campo FS está configurado en /; Esto afecta la forma en que lee los campos. El separador de campo de salida OFS también se establece en /; Esto afecta la forma en que imprime los registros. La siguiente declaración dice imprimir la última columna ( NFes el número de campos en el registro, por lo que también es el índice del último campo), así como el registro completo ( $0es el registro completo); los imprimirá con la OFS entre ellos. Luego se edita la lista sort, que se trata /como el separador de campo, ya que tenemos el nombre de archivo primero en el registro, se ordenará por eso. Luego, las cutimpresiones solo los campos 2 hasta el final, nuevamente se tratan /como el separador de campo.

Shawn J. Goff
fuente
3
Como esto es con find (1), puede omitir la parte awk y usar-printf '%f/%p\n'
camh
de hecho nuestra configuración es un poco más complicada. Incluye profundidades de subdirección variables. Editó la pregunta para reflejar este hecho. Mis disculpas por no incluir esto al principio.
Unode
1
@Unode: la solución de Shawn maneja bien la profundidad variable, es la solución canónica a este problema (hasta pequeñas variaciones).
Gilles 'SO- deja de ser malvado'
4

Usaría los archivos '-printf' para generar el nombre y la ruta, ordenar por nombre y cortar el nombre en un último paso. '###' es solo un marcador, para ayudar a cortar.

find -name "*.txt" -printf "%f###%p\n" | sort -n | sed 's/.*###//'

% f imprime el nombre del archivo,% p la ruta completa.

Simplifiqué el comando de búsqueda para ponerlo en una línea, por supuesto, dejarías la ! -path "./build*"parte.

usuario desconocido
fuente
3

En zsh ≥4.3.10:

print -l -- **/*.txt~build*(oe\''REPLY=${REPLY:t}'\')
  • **/*.txtcoincide *.txten el directorio actual y sus subdirectorios de forma recursiva .
  • ~build* excluye coincidencias cuyo texto comienza con build*(me gusta ! -path './build*'). (Necesitas setopt extended_globprimero)
  • (oe\''…'\')es un calificador global de clasificación . REPLY=…construye la cadena para ordenar de la cadena para devolver.
  • ${REPLY:t}es el nombre base ("cola") de la ruta.
Gilles 'SO- deja de ser malvado'
fuente
Mucha magia concatenada. Interesante pero estamos limitados a la sintaxis sh. +1
unode