Error de script Bash con cadenas con rutas que tienen espacios y comodines

Tengo problemas para entender los conceptos básicos del script Bash. Esto es lo que tengo hasta ahora:

#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done 

Todo lo que quiero hacer es enumerar todos los archivos .txt en un bucle for para poder hacer cosas con ellos. Pero el espacio en my directory y el asterisco en *.txt simplemente no están jugando bien. Intenté usarlo con y sin comillas dobles, con y sin llaves en los nombres de variables y todavía no puedo imprimir todos los archivos .txt.

Este es un algo muy básico, pero todavía estoy luchando porque estoy cansado y no puedo pensar con claridad.

¿Qué estoy haciendo mal?

He podido aplicar con éxito el script de arriba si mis ARCHIVOS no tienen un espacio o un asterisco … Tuve que experimentar con o sin el uso de comillas dobles y llaves para que funcione. Pero en el momento en que tengo espacios y un asterisco, se estropea todo.

Responder

Entre comillas, el * no se expandirá a una lista de archivos. Para utilizar un comodín con éxito, debe estar fuera de las comillas.

Incluso si el comodín se expandiera, la expresión "${FILES}" resultaría en una sola cadena, no una lista de archivos.

Un enfoque que funcionaría sería:

#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done 

En lo anterior, los nombres de archivos con espacios u otros los caracteres se manejarán correctamente.

Un enfoque más avanzado podría usar matrices bash:

#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done 

En este caso, FILES es una matriz de nombres de archivos. Los parens que rodean la definición la convierten en una matriz. Tenga en cuenta que * está fuera de las comillas. La construcción "${FILES[@]}" es un caso especial: se expandirá a una lista de cadenas donde cada cadena es uno de los nombres de archivo. Los nombres de archivo con espacios u otros caracteres difíciles se manejarán correctamente.

Comentarios

  • genial que funcionó
  • It ‘ s vale la pena señalar que si ‘ está pasando rutas como esta a través de funciones, debe asegurarse de citar la variable por sí sola en lugar de concatenarlo como parte de una cadena más grande: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = breaks

Respuesta

Si bien el uso de arreglos como lo muestra John1024 tiene mucho más sentido, aquí también puede usar el operador split + glob (dejando una variable escalar sin comillas).

Dado que solo desea la parte global de ese operador, debe deshabilitar la parte dividir :

#! /bin/sh - # that also works in any sh, so you don"t even need to have or use bash file_pattern="/home/john/my directory/*.txt" # all uppercase variables should be reserved for environment variables IFS="" # disable splitting for f in $file_pattern # here we"re not quoting the variable so # we"re invoking the split+glob operator. do printf "%s\n" "$f" # avoid the non-reliable, non-portable "echo" done 

Responder

Lo que puede hacer es dejar solo los caracteres comodín fuera de las comillas.
Algo como:
para a en «archivos con espacios» * «. txt «
hacer
procesamiento
hecho
Si los comodines se expanden a espacios, entonces no necesitará usar un enfoque de archivo por línea, como usar ls -l para generar la lista de archivos y use bash read para obtener cada archivo.

Respuesta

Solución basada en una sola línea, (para ejecutar en Terminal):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

para su caso / OP «s, cambie "./" por "/home/john/my directory/"

Para usar en un archivo de script:

 #!/bin/bash /usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done; unset f;  

La funcionalidad anterior también se puede lograr de esta manera (recomendada):

 #!/bin/bash while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0); unset f;  

Breve / Breve DESCRIPCIÓN:

"./": este es el directorio actual. especificar una ruta de directorio.
-not -type d: aquí, -not lo está configurando para omitir el siguiente tipo mencionado, & el siguiente -type mencionado es d = directorios, por lo que se omitirá directorios. Utilice f en lugar de d para omitir archivos. Utilice l en lugar de d para omitir archivos de enlace simbólico.
-maxdepth 1: lo está configurando para buscar archivos dentro del nivel de directorio actual (también conocido como: uno) solamente. Para encontrar el archivo dentro de cada & primer nivel de subdirectorio, establezca maxdepth en 2. Si no se utiliza -maxdepth, buscará de forma recursiva (dentro de los subdirectorios), etc.
-iname "*.jpg": aquí el -iname lo está configurando para buscar archivos e ignorarlos (superior / inferior) -case en nombre de archivo / extensión. -name no ignora las mayúsculas y minúsculas. -lname busca enlaces simbólicos. etc.
-print0: imprime el nombre de la ruta del archivo actual en la salida estándar, seguido de un carácter ASCII NUL (código de carácter 0), que detectar más tarde utilizando read en while.
IFS=: aquí se usa en caso de que un nombre de archivo termine con un espacio. Estamos usando NUL / "" / \0 con IFS para detectar cada nombre de archivo encontrado. Como » find » está configurado para separarlos con \0 que es producido por -print0.
read -r -d $"\0" fileName: $"\0" es "" / NUL. El -r se usó en caso de que el nombre de un archivo tuviera una barra invertida.
read [-ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name. ..] […]
-r La barra invertida no actúa como carácter de escape. La barra invertida se considera parte de la línea. En particular, un par de barra invertida-nueva línea no puede usarse como una continuación de línea.
done < <(...): Sustitución de proceso usada aquí para enviar / salida de tubería de » busque » en la » lea » de » mientras que » -loop. Más información: https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html, https://wiki.bash-hackers.org/syntax/expansion/proc_subst, https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB, https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP


En otra respuesta de @ John1024, ha mostrado una gran solución basada en bash, que no usa » busque «, una utilidad externa.
» busque » es muy eficaz & rápido, lo prefiero cuando hay demasiados archivos para manejar.

en la solución de @ John1024, imprimirá el línea de regla coincidente cuando no hay ningún archivo en el directorio, por lo que debajo de [ ! -e "${f}" ]... se usa la línea para omitir eso,
aquí hay una solución de una sola línea para usar en Terminal directamente:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Aquí hay una secuencia de comandos:

 #!/bin/bash DIR="/home/john/my directory/"; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; # your codes/commands, etc }; done; unset DIR;  

Nota: si el directorio en DIR tiene "/" barra (indicador de directorio) al final, entonces en la regla de coincidencia , nuevamente, usar "/" no es necesario,
O, haga lo contrario: en DIR no use el "/" al final y utilícelo en la regla de coincidencia "$DIR"/*.txt


Esa comprobación adicional mediante [ ! -e "${f}" ]... puede evitarse el código, si es inferior a shell-option (también conocido como: » shopt «) se utiliza o habilita:
shopt -s nullglob

Si un script cambió el estado de una tienda, entonces en otro programa de script basado en bash se crean problemas inesperados / imprevistos.

Para tener un comportamiento consistente en todos los scripts que usa bash, el estado de bash-shell-option debe grabarse / guardarse dentro de su script, y una vez que haya terminado de usar sus funciones primarias en su script, entonces esa opción de shell debe volver a la configuración anterior.

Usamos comillas inversas (también conocidas como: acento grave, también conocidas como comillas inversas) `...` sustitución de comando (para códigos internos de comando bash, etc.), para no generar un nuevo sub-shell, retener el significado literal de la barra invertida, para un soporte más amplio (también conocido como: portabilidad), etc., ya que los comandos bash internos basados en comillas invertidas, etc., a menudo se pueden ejecutar en el mismo shell que el script, etc., por lo que es un poco más rápido & mejor, y también mejor para el propósito que estamos manejando aquí. Si prefiere $(...) la sustitución de comandos, utilícela, cualquiera tiene libertad & derecho a elegir lo que prefiera, evitar, etc. Más información : aquí .

Entonces se muestra nuevamente el script anterior, & esta vez con la configuración anterior de una tienda restaurada, antes de finalizar el script:

 #!/bin/bash DIR="/home/john/my directory/"; ub="/usr/bin"; # shopt = shell-option(s). # Setting-up "previous-nullglob" state to "enabled"/"on"/"1": p_nullglob=1; # The "shopt -s" command output shows list of enabled shopt list, so if # nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0 [ "`shopt -s | ${ub}/grep nullglob`" == "" ] && p_nullglob=0; # Enabling shell-options "nullglob": shopt -s nullglob; # previous code, but without the extra checking [ ! -e "${f}" ]... line: for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; # As we have utilized enabled nullglob shopt, its now in enabled state, # so if previously it was disabled only-then we will disable it: [ "$p_nullglob" -eq "0" ] && shopt -u nullglob; unset DIR; unset p_nullglob ub;  

Salida de shopt -p shoptName (por ejemplo: shopt -p dotglob) puede ser
cualquiera, esto: shopt -u shoptName (el u es unset / disabled / off / 0)
o esto: shopt -s shoptName ( el s es set / enabled / on / 1)
La posición de la letra "s" o "u" siempre está en 7 (porque, en bash, un La posición de la letra de la cadena comienza en 0, es decir, la primera letra de una cadena está en la posición 0)
Nosotros puede obtener este "u" o y almacenarlo en una variable, para que podamos usarlo para restaurar el estado anterior.
Y si aplicamos esta forma (mencionada anteriormente) de guardar / restaurar el estado de la tienda, entonces podemos evite usar la herramienta externa "grep".

Para ver el archivo "txt" que comienza con ".", es decir, para ver el archivo "txt" oculto, necesitamos habilitar "dotglob" shopt.

Entonces, esta vez a continuación, "dotglob" se incluye & habilitado para mostrar archivos HIDDEN "txt":

 #!/bin/bash DIR="/home/john/my directory/"; p_nullglob="u"; pSS="`shopt -p nullglob`"; [ "${pSS:7:1}" == "s" ] && p_nullglob="s"; p_dotglob="u"; pSS="`shopt -p dotglob`"; [ "${pSS:7:1}" == "s" ] && p_dotglob="s"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; [ "$p_nullglob" == "u" ] && shopt -u nullglob; [ "$p_dotglob" == "u" ] && shopt -u dotglob; unset DIR; unset p_nullglob p_dotglob pSS;  

Hay una forma más sencilla de guardar / restaurar shopt opción / valor.
Isaac publicó aquí , cómo guardar + restaurar Env / Shopt variable / option state / value.

Guardando el estado shopt de » nullglob «:

... # your primary-function codes/commands, etc lines
Restaurando el estado anterior de la tienda de » nullglob «, antes de salir del script:
eval "$p_nullglob" ;

Se pueden guardar varios estados de la tienda de esta manera:
p_multipleShopt="`shopt -p nullglob dotglob`";
y el proceso de restauración es el mismo que antes:
eval "$p_multipleShopt" ;

Guarde TODOS los estados de la tienda de esta manera:
p_allShopt="`shopt -p`";
y el proceso de restauración es el mismo que antes:
eval "$p_allShopt" ;

Así que aquí hay otra solución basada en bash:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; eval "$p_allShopt" ; unset DIR p_allShopt;  

Usar eval es seguro arriba, ya que la variable "$p_allShopt" no contiene datos proporcionados por un usuario o datos que no son- desinfectado, esa var contiene la salida del comando interno de bash shopt.
Si aún desea evitar eval, utilice a continuación para lution:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; while IFS= read -a oneLine ; do { ${oneLine} ; }; done < <(echo "$p_allShopt") ; unset DIR p_allShopt oneLine;  

Pocas (otras) SHOPT relacionados que pueden ser útiles son:

  • nocaseglob: si se establece, Bash coincide con los nombres de archivo en un no distingue entre mayúsculas y minúsculas al realizar la expansión del nombre de archivo.
  • nocasematch: si se establece, Bash hace coincidir los patrones de manera que no distingue entre mayúsculas y minúsculas al realizar la comparación mientras se ejecuta case o [[ comandos condicionales, cuando se realizan expansiones de palabras de sustitución de patrones o cuando se filtran posibles finalizaciones como parte de la finalización programable.
  • dotglob: si se establece, Bash incluye nombres de archivo que comienzan con un ‘.’ en los resultados de la expansión del nombre de archivo. Los nombres de archivo ‘.’ y ‘..’ siempre deben coincidir explícitamente, incluso si dotglob está configurado.
  • nullglob: si se establece , Bash permite que los patrones de nombre de archivo que no coinciden con archivos se expandan a una cadena nula, en lugar de a sí mismos.
  • extglob: si se establece, las funciones de coincidencia de patrones extendidas descritas anteriormente (consulte Coincidencia de patrones ) están habilitados.
  • globstar: si se establece, el patrón ‘**’ usado en un contexto de expansión de nombre de archivo coincidirá con todos los archivos y cero o más directorios y subdirectorios. Si el patrón es seguido por un ‘/’, solo los directorios y subdirectorios coinciden.

Respuesta

Cuando desee procesar un conjunto de archivos, considere que su nombre puede haber un espacio u otro código de escape estará allí, así que antes de comenzar su proceso, como for loop o find command establece IFS bash env variable en:

IFS=$(echo -en "\n\b") 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *