Cómo recorrer los archivos en el directorio y cambiar la ruta y agregar sufijo al nombre de archivo

563

Necesito escribir un script que inicie mi programa con diferentes argumentos, pero soy nuevo en Bash. Comienzo mi programa con:

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt].

Aquí está el pseudocódigo de lo que quiero hacer:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end for
end for

Así que estoy realmente intrigado sobre cómo crear un segundo argumento a partir del primero, por lo que parece dataABCD_Log1.txt e iniciar mi programa.

Dobrobobr
fuente

Respuestas:

739

Primero un par de notas: cuando se usa Data/data1.txtcomo argumento, ¿debería serlo realmente /Data/data1.txt(con una barra inclinada)? Además, ¿debería el bucle externo escanear solo los archivos .txt o todos los archivos en / Data? Aquí hay una respuesta, asumiendo /Data/data1.txty solo archivos .txt:

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

Notas:

  • /Data/*.txtse expande a las rutas de los archivos de texto en / Data ( incluida la parte / Data /)
  • $( ... ) ejecuta un comando de shell e inserta su salida en ese punto en la línea de comando
  • basename somepath .txtgenera la parte base de somepath, con .txt eliminado del final (por ejemplo, /Data/file.txt-> file)

Si necesita ejecutar MyProgram con en Data/file.txtlugar de /Data/file.txt, use "${filename#/}"para eliminar la barra diagonal inicial . Por otro lado, si realmente Datano /Dataquieres escanear, simplemente úsalo for filename in Data/*.txt.

Gordon Davisson
fuente
1
Aparentemente basename -ses una extensión no estándar: editaré mi respuesta para usar la sintaxis estándar.
Gordon Davisson
21
Si no se encuentran archivos / coincide con el comodín, encuentro que el bloque de ejecución de bucles todavía se ingresa una vez con filename = "/Data/*.txt". ¿Cómo puedo evitar esto?
Oliver Pearmain
20
@OliverPearmain Utilice shopt -s nullglobantes del bucle (y shopt -u nullglobdespués para evitar problemas más adelante), o agregue if [[ ! -e "$filename ]]; then continue; fial principio del bucle, para omitir los archivos inexistentes.
Gordon Davisson
99
Esto no funciona cuando hay archivos que contienen espacios en blanco en su nombre.
Isa Hassen
44
@Isa Debería funcionar con espacios en blanco, siempre y cuando todas las comillas dobles estén en su lugar. Si deja de lado las comillas dobles, tendrá problemas con los espacios en blanco.
Gordon Davisson el
345

Perdón por el nigromancio del hilo, pero cada vez que itera sobre los archivos con glob, es una buena práctica evitar el caso de la esquina donde el glob no coincide (lo que hace que la variable de bucle se expanda a la cadena del patrón de glob (no coincidente)).

Por ejemplo:

for filename in Data/*.txt; do
    [ -e "$filename" ] || continue
    # ... rest of the loop body
done

Referencia: trampas Bash

Cong Ma
fuente
8
Esto sigue siendo una advertencia oportuna. Pensé que había creado mi script incorrectamente, pero tenía la extensión de mi archivo en minúscula en lugar de mayúscula, no encontró ningún archivo y devolvió el patrón global. ugh
RufusVS
55
Como se usa la etiqueta Bash: ¡para eso shopt nullglobestá! (o shopt failglobpuede usarse también, según el comportamiento que desee).
gniourf_gniourf
Además, esto también verifica los archivos eliminados durante el procesamiento, antes de que el ciclo los alcance.
loxaxs
1
También maneja el caso en el que tiene un directorio llamadodir.txt
vidstige
75
for file in Data/*.txt
do
    for ((i = 0; i < 3; i++))
    do
        name=${file##*/}
        base=${name%.txt}
        ./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
    done
done

La name=${file##*/}sustitución ( expansión del parámetro de shell ) elimina el nombre de ruta principal hasta el último /.

La base=${name%.txt}sustitución elimina el final .txt. Es un poco más complicado si las extensiones pueden variar.

Jonathan Leffler
fuente
3
Creo que hay un error en tu código. La línea debe ser base=${name%.txt}, en lugar de base=${base%.txt}.
caseklim
44
@CaseyKlimkowsky: Sí; cuando el código y los comentarios no están de acuerdo, al menos uno de ellos está equivocado. En este caso, creo que es solo uno: el código; a menudo, en realidad son los dos los que están mal. Gracias por señalar eso; Lo he arreglado
Jonathan Leffler
8

Puede usar la opción de resultados de búsqueda separada por nulo con lectura para iterar sobre las estructuras de directorio de forma segura.

#!/bin/bash
find . -type f -print0 | while IFS= read -r -d $'\0' file; 
  do echo "$file" ;
done

Entonces para tu caso

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done

Adicionalmente

#!/bin/bash
while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done < <(find . -maxdepth 1 -type f  -print0)

ejecutará el ciclo while en el alcance actual del script (proceso) y permitirá que la salida de find se use para establecer variables si es necesario

James
fuente
2
$'\0'Es una forma extraña de escribir ''. Que se está perdiendo IFS=y el -rcambio a read: su estado de lectura debe ser: IFS= read -rd '' file.
gniourf_gniourf
Me imagino que algunos necesitarían buscar $'\0'y distribuir algunos puntos de pila. Voy a hacer las ediciones que señaló. ¿Cuáles son los efectos nocivos de no IFS=intentarlo? echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; doneParece que no hay ninguno. Además, -r veo que a menudo es predeterminado, pero no pude encontrar un ejemplo de lo que impide que suceda.
James
44
IFS=es necesario en caso de que un nombre de archivo termine con un espacio: try is with touch 'Prospero '(tenga en cuenta el espacio final). También necesita el -rinterruptor en caso de que un nombre de archivo tenga una barra invertida: pruébelo con touch 'Prospero\n'.
gniourf_gniourf
-1

Parece que estás intentando ejecutar un archivo de Windows (.exe). Seguramente deberías estar usando powershell. De todos modos, en un bash shell de Linux, bastará con una línea simple.

[/home/$] for filename in /Data/*.txt; do for i in {0..3}; do ./MyProgam.exe  Data/filenameLogs/$filename_log$i.txt; done done

O en una fiesta

#!/bin/bash

for filename in /Data/*.txt; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done
usuario3183111
fuente