Los shells como Bash y Zsh expanden comodines en argumentos, tantos argumentos como coincidan con el patrón:
$ echo *.txt
1.txt 2.txt 3.txt
Pero, ¿qué sucede si solo quiero que se devuelva la primera coincidencia, no todas las coincidencias?
$ echo *.txt
1.txt
No me importan las soluciones específicas de shell, pero me gustaría una solución que funcione con espacios en blanco en los nombres de archivo.
Respuestas:
Una forma sólida en bash es expandirse en una matriz y generar solo el primer elemento:
(Incluso puede simplemente
echo $files
, un índice faltante se trata como [0]).Esto maneja de forma segura el espacio / tabulador / nueva línea y otros metacaracteres al expandir los nombres de archivo. Tenga en cuenta que la configuración regional vigente puede alterar lo que es "primero".
También puede hacer esto interactivamente con una función de finalización de bash :
Esto vincula la
_echo
función para completar argumentos alecho
comando (anulando la finalización normal). Se agrega un "*" adicional en el código anterior, solo puede presionar la pestaña en un nombre de archivo parcial y, con suerte, sucederá lo correcto.El código es un poco complicado, en lugar de establecer o asumir
nullglob
(shopt -s nullglob
) comprobamos quecompgen -G
puede expandir el glob a algunas coincidencias, luego expandimos de forma segura en una matriz y finalmente establecemos COMPREPLY para que las citas sean robustas.Puede parte hacer esto (mediante programación expandir un pegote) con fiesta de
compgen -G
, pero no es tan robusta que da salida a la salida estándar sin comillas.Como de costumbre, la finalización es bastante complicada, esto interrumpe la finalización de otras cosas, incluidas las variables de entorno (consulte la
_bash_def_completion()
función aquí para obtener detalles sobre cómo emular el comportamiento predeterminado).También podría usarlo
compgen
fuera de una función de finalización:Un punto a tener en cuenta es que "~" no es un problema, se maneja con bash en una etapa separada de expansión, al igual que las variables $ y otras expansiones.
compgen -G
solo hace globing de nombre de archivo, perocompgen -W
le brinda toda la expansión predeterminada de bash, aunque posiblemente demasiadas expansiones (incluyendo``
y$()
). A diferencia-G
,-W
se cita con seguridad (no puedo explicar la disparidad). Dado que el propósito-W
es que expande los tokens, esto significa que expandirá "a" a "a" incluso si no existe tal archivo, por lo que tal vez no sea ideal.Esto es más fácil de entender, pero puede tener efectos secundarios no deseados:
Luego:
touch $'curious \n filename'
echo curious*
tabTenga en cuenta el uso de
printf %q
para citar de forma segura los valores.Una última opción es usar una salida delimitada por 0 con utilidades GNU (consulte las preguntas frecuentes de bash ):
Esta opción le da un poco más de control sobre el orden de clasificación (el orden al expandir un globo estará sujeto a su ubicación /
LC_COLLATE
y puede o no doblar mayúsculas y minúsculas), pero de lo contrario es un martillo bastante grande para un problema tan pequeño ;-)fuente
En zsh, usa el
[1]
calificador glob . Tenga en cuenta que, aunque este caso especial devuelve como máximo una coincidencia, sigue siendo una lista, y los globos no se expanden en contextos que esperan una sola palabra, como asignaciones (asignaciones de matriz a un lado).En ksh o bash, puede rellenar toda la lista de coincidencias en una matriz y usar el primer elemento.
En cualquier shell, puede establecer los parámetros posicionales y usar el primero.
Esto cambia los parámetros posicionales. Si no quieres eso, puedes usar una subshell.
También puede usar una función, que tiene su propio conjunto de parámetros posicionales.
fuente
*.txt([1,n])
Tratar:
Una nota de que la expansión del nombre de archivo se ordena de acuerdo con la secuencia de clasificación vigente en la configuración regional actual.
fuente
Una solución simple:
O úsalo
printf
si lo prefieres.fuente
Me topé con esta vieja pregunta mientras me preguntaba lo mismo. Terminé con esto:
echo $(ls *.txt | head -n1)
Puede, por supuesto, reemplazar
head
contail
y-n1
con cualquier otro número.Lo anterior no funcionará si está trabajando con archivos que tienen nuevas líneas en su nombre. Para trabajar con líneas nuevas puede usar cualquiera de estos:
ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g'
(No funciona en BSD)ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
echo -e "$(ls -b *.txt | head -n1)"
(Funciona con cualquier personaje especial)fuente
Un caso de uso que a menudo encuentro es definir el directorio superior / inferior después de la expansión global (por ejemplo, un directorio lleno de SDK versionados o herramientas de compilación). En esta situación, generalmente querré guardar ese nombre de directorio en una variable para usarlo en algunos lugares dentro de un script de shell.
Este comando generalmente lo hace por mí:
Descargo de responsabilidad: la expansión Glob no va a ordenar sus carpetas por semver; has sido advertido. Esto es genial si tiene una
Dockerfile
única versión de un directorio, pero esa versión de los directorios puede variar de una imagen a otra 🤷fuente
mkdir "$(echo one; echo two)"
y vea a qué me refiero.tail
?dirname
solo toma un solo nombre de ruta, por lo que no puede confiar en que funcione en varios nombres de ruta a menos que sepa que su implementación particular lo admite.