Archivos Glob * sin * usar un shell

Aug 22 2020

Quiero enumerar archivos en un subdirectorio determinado, pero lo hago como parte de un docker execcontenedor dentro de una ventana acoplable, por lo que no quiero molestarme en iniciar un shell que realmente no necesito. ¿Es posible encontrar todas las coincidencias para un glob con una herramienta de línea de comandos simple, y no solo un shell?

Por ejemplo, mi invocación actual es bash -l -c 'echo /usr/local/conda-meta/*.json'. ¿Es posible simplificar esto usando una herramienta comúnmente disponible, resultando en algo como globber /usr/local/conda-meta/*.json, que sería mucho más simple y liviano?

Respuestas

16 StéphaneChazelas Aug 23 2020 at 05:12

shes simple y comúnmente disponible. shes la herramienta que se invoca para analizar líneas de comando en cosas como system(cmdline)en muchos idiomas. Muchos sistemas operativos, incluidos algunos GNU, han dejado de usar bash(el shell GNU) para implementar shdebido a que se ha vuelto demasiado hinchado para hacer esa simple cosa de analizar líneas de comando e interpretar shscripts POSIX .

Su bash -l -c 'echo /usr/local/conda-meta/*.json'línea de comando posiblemente ya esté siendo interpretada por una shinvocación. Así que posiblemente puedas hacer:

printf '%s\n' /usr/local/conda-meta/*.json

directamente. Si no:

sh -c 'printf "%s\n" /usr/local/conda-meta/*.json'

También puedes usar findaquí. findno hace globbing pero puede informar nombres de archivos que coinciden con patrones similares a los de shell.

LC_ALL=C find /usr/local/conda-meta/. ! -name . -prune -name '*.json'

O con algunas findimplementaciones:

LC_ALL=C find /usr/local/conda-meta -mindepth 1 -maxdepth 1 -name '*.json'

(tenga en cuenta que lo LC_ALL=Cnecesario aquí para que *coincida con cualquier secuencia de bytes, no solo con aquellos que forman caracteres válidos en la configuración regional actual, es una construcción de shell. Si esa línea de comando no es interpretada por un shell, es posible que deba cambiarla a env LC_ALL=C find...)

Algunas diferencias con los globos de concha:

  • la lista de archivos no está ordenada
  • los archivos ocultos están incluidos (puede agregar un ! -name '.*'para excluirlos)
  • no obtiene salida si no hay un archivo coincidente. los globs tienen esa característica errónea de que dejan el patrón como está sin expandir en ese caso.
  • con la primera variante (estándar), los archivos se generarán como /usr/local/conda-meta/./file.json.
  • algunos globs como x*/y/../*zno se traducen fácilmente (también tenga en cuenta el comportamiento diferente con respecto a los enlaces simbólicos a directorios en ese caso).

En cualquier caso, no puede utilizar echopara generar datos arbitrarios.

Mi siguiente pregunta sería: ¿qué vas a hacer con esa salida? Con echo, está generando esas rutas de archivo separadas por caracteres SPC, y con my printfo findsuperior, delimitadas por caracteres NL. Ambos NLy SPCson caracteres perfectamente válidos en los nombres de archivo, por lo que esas salidas no son fiables después del procesamiento. Puede usar en '%s\0'lugar de '%s\n'(o usar find's -print0si es compatible), no apto para mostrar a un usuario, pero posprocesable.

En términos de eficiencia, comparando Ubuntu 20.04 /bin/sh(dash 0.5.10.2) con su find(GNU find4.7.0).

Tiempo de inicio:

$ time (repeat 1000 sh -c '') ( repeat 1000; do; sh -c ''; done; ) 0.91s user 0.66s system 105% cpu 1.483 total $ time (repeat 1000 find . -quit)
( repeat 1000; do; find . -quit; done; )  1.35s user 1.25s system 103% cpu 2.507 total

Globbing algunos jsonarchivos:

$ TIMEFMT='%U user %S system %P cpu %*E total' $ time (repeat 1000 sh -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
0.95s user 0.72s system 105% cpu 1.587 total
$ time (repeat 1000  find /usr/share/iso-codes/json -mindepth 1 -maxdepth 1 -name '*.json') > /dev/null
1.34s user 1.35s system 103% cpu 2.599 total

Incluso bashes apenas más lento que findaquí:

$ time (repeat 1000 bash -c 'printf "%s\n" /usr/share/iso-codes/json/*.json') > /dev/null
1.53s user 1.36s system 102% cpu 2.808 total

Por supuesto, YMMV dependiendo del sistema, implementación, versión de las respectivas utilidades y las bibliotecas a las que están vinculadas.

Ahora, en la nota histórica, el nombre global en realidad proviene del nombre de una utilidad llamada globen las primeras versiones de Unix a principios de los 70. Se ubicó en /etcy fue invocado por shcomo ayuda para expandir patrones de comodines.

Encontrarás algunos proyectos en línea para revivir ese caparazón muy antiguo, como https://etsh.nl/. Más como un ejercicio de arqueología, podría construir la globutilidad desde allí y luego poder hacer:

glob printf '%s\n' '/usr/local/conda-meta/*.json'

Sin embargo, algunas notas de advertencia.

  • esos son globos antiguos, [!x](y mucho menos [^x]) no son compatibles.
  • no es seguro de 8 bits. En realidad, el octavo bit se usa para escapar de los operadores glob ( $'\xe9*'coincidiría con lo mismo que i*, $'\xaa*'coincidiría con los nombres de archivo que comienzan con *; el shell establecería ese octavo bit para los caracteres entre comillas antes de invocar glob)
  • rangos como [a-f]coincidencia en el valor de byte en lugar del orden de clasificación (en la práctica, eso es generalmente una ventaja en mi opinión).
  • Los globs que no coinciden dan como resultado un No matcherror (nuevamente, probablemente preferiblemente, eso es algo que fue roto por el shell de Bourne a finales de los 70).

La globfuncionalidad se trasladó más tarde al shell comenzando con el shell PWB y el shell Bourne a finales de los 70. Más tarde, se agregaron algunas funciones fnmatch()y glob()a la biblioteca C para permitir que esa característica se use desde otras aplicaciones, pero no conozco una utilidad estándar o común que sea una interfaz simple para esa función. Incluso perlsolía invocar cshen sus inicios para expandir patrones glob.

7 BasileStarynkevitch Aug 23 2020 at 14:50

Archivos Glob sin usar un shell

La documentación obvia para leer es glob (7) .

Puede escribir o usar un programa en C llamando a fnmatch (3) , glob (3) , nftw (3) , stat (2) , readdir (3)

Si codifica en Guile , Python , Go , Rust , Ocaml , Common Lisp (por ejemplo, SBCL ) ... encontrará funciones similares. Con C ++, mire POCO y Qt .

Supongo que está utilizando un sistema Linux. Por cierto, mi shell interactivo es zsh (cuyas instalaciones de autocompletado son preferibles en mi humilde opinión).