usar script para matrices listas lista for ejemplos crear como bucle array bash loops whitespace

script - ¿Cómo puedo escapar del espacio en blanco en una lista de bucles bash?



listas en bash linux (20)

Tengo un script bash shell que recorre todos los directorios secundarios (pero no archivos) de un directorio determinado. El problema es que algunos de los nombres de directorio contienen espacios.

Aquí están los contenidos de mi directorio de prueba:

$ls -F test Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt

Y el código que recorre los directorios:

for f in `find test/* -type d`; do echo $f done

Aquí está el resultado:

test/Baltimore test/Cherry Hill test/Edison test/New York City test/Philadelphia

Cherry Hill y la ciudad de Nueva York se tratan como 2 o 3 entradas por separado.

Intenté citar los nombres de los archivos, así:

for f in `find test/* -type d | sed -e ''s/^//"/'' | sed -e ''s/$//"/''`; do echo $f done

pero fue en vano.

Tiene que haber una manera simple de hacer esto.

Las respuestas a continuación son geniales. Pero para hacerlo más complicado, no siempre quiero usar los directorios que figuran en mi directorio de prueba. A veces quiero pasar los nombres del directorio como parámetros de línea de comandos.

Tomé la sugerencia de Charles de establecer el IFS y se me ocurrió lo siguiente:

dirlist="${@}" ( [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$''/n'' for d in $dirlist; do echo $d done )

y esto funciona bien a menos que haya espacios en los argumentos de la línea de comandos (incluso si esos argumentos están entre comillas). Por ejemplo, llamar al script de esta manera: test.sh "Cherry Hill" "New York City" produce el siguiente resultado:

Cherry Hill New York City


¿Por qué no solo poner

IFS=''/n''

frente al comando for? Esto cambia el separador de campo de <Espacio> <Pestaña> <Nueva línea> a solo <Nueva línea>


Acabo de tener un problema de variante simple ... Convierta archivos de .flv tipeado en .mp3 (bostezo).

for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done

encontrar recursivamente todos los archivos flash del usuario de Macintosh y convertirlos en audio (copiar, no transcodificar) ... es como el anterior, señalando que se lee en lugar de solo ''para el archivo en ''escapará.


Aquí hay una solución simple que maneja pestañas y / o espacios en blanco en el nombre del archivo. Si tiene que tratar con otros caracteres extraños en el nombre del archivo como nuevas líneas, elija otra respuesta.

El directorio de prueba

ls -F test Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt

El código para ir a los directorios

find test -type d | while read f ; do echo "$f" done

El nombre del archivo debe ser citado ( "$f" ) si se usa como argumento. Sin comillas, los espacios actúan como separador de argumentos y se otorgan múltiples argumentos al comando invocado.

Y el resultado:

test/Baltimore test/Cherry Hill test/Edison test/New York City test/Philadelphia


Bueno, veo demasiadas respuestas complicadas. No quiero pasar el resultado de la utilidad find o escribir un bucle, porque find tiene la opción "exec" para esto.

Mi problema era que quería mover todos los archivos con extensión dbf a la carpeta actual y algunos de ellos contenían espacios en blanco.

Lo abordé así:

find . -name /*.dbf -print0 -exec mv ''{}'' . '';''

Parece mucho simple para mí


Convierte la lista de archivos en una matriz Bash. Esto utiliza el enfoque de Matt McClure para devolver una matriz de una función de Bash: http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html El resultado es una forma para convertir cualquier entrada de varias líneas a una matriz de Bash.

#!/bin/bash # This is the command where we want to convert the output to an array. # Output is: fileSize fileNameIncludingPath multiLineCommand="find . -mindepth 1 -printf ''%s %p//n''" # This eval converts the multi-line output of multiLineCommand to a # Bash array. To convert stdin, remove: < <(eval "$multiLineCommand" ) eval "declare -a myArray=`( arr=(); while read -r line; do arr[${#arr[@]}]="$line"; done; declare -p arr | sed -e ''s/^declare -a arr=//'' ) < <(eval "$multiLineCommand" )`" for f in "${myArray[@]}" do echo "Element: $f" done

Este enfoque parece funcionar incluso cuando hay malos caracteres, y es una forma general de convertir cualquier entrada a una matriz de Bash. La desventaja es que si la entrada es larga, podría exceder los límites de tamaño de la línea de comando de Bash o consumir grandes cantidades de memoria.

Los enfoques donde el bucle que eventualmente está trabajando en la lista también tiene la lista introducida tienen la desventaja de que la lectura de stdin no es fácil (como pedirle al usuario que ingrese), y el ciclo es un nuevo proceso por lo que puede preguntarse por qué usted establece dentro del ciclo no están disponibles después de que termine el ciclo.

Tampoco me gusta configurar IFS, puede estropear otro código.


Esto es extremadamente complicado en Unix estándar, y la mayoría de las soluciones se ven afectadas por líneas nuevas o algún otro personaje. Sin embargo, si está utilizando el conjunto de herramientas GNU, puede aprovechar la opción de find -print0 y usar xargs con la opción correspondiente -0 (menos-cero). Hay dos caracteres que no pueden aparecer en un nombre de archivo simple; esos son barra inclinada y NUL ''/ 0''. Obviamente, la barra aparece en los nombres de las rutas, por lo que la solución GNU de usar un NUL ''/ 0'' para marcar el final del nombre es ingeniosa y a prueba de tontos.


Necesitaba el mismo concepto para comprimir secuencialmente varios directorios o archivos de una determinada carpeta. He resuelto usar awk para analizar la lista de ls y para evitar el problema de espacio en blanco en el nombre.

source="/xxx/xxx" dest="/yyy/yyy" n_max=`ls . | wc -l` echo "Loop over items..." i=1 while [ $i -le $n_max ];do item=`ls . | awk ''NR==''$i'''' ` echo "File selected for compression: $item" tar -cvzf $dest/"$item".tar.gz "$item" i=$(( i + 1 )) done echo "Done!!!"

¿Qué piensas?


No almacene listas como cadenas; guárdelos como matrices para evitar toda esta confusión de delimitadores. Aquí hay un script de ejemplo que operará en todos los subdirectorios de prueba o en la lista proporcionada en su línea de comando:

#!/bin/bash if [ $# -eq 0 ]; then # if no args supplies, build a list of subdirs of test/ dirlist=() # start with empty list for f in test/*; do # for each item in test/ ... if [ -d "$f" ]; then # if it''s a subdir... dirlist=("${dirlist[@]}" "$f") # add it to the list fi done else # if args were supplied, copy the list of args into dirlist dirlist=("$@") fi # now loop through dirlist, operating on each one for dir in "${dirlist[@]}"; do printf "Directory: %s/n" "$dir" done

Ahora intentemos esto en un directorio de prueba con una curva o dos:

$ ls -F test Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ this is a dirname with quotes, lfs, escapes: "/'''?''?/e/n/d/ this is a file, not a directory $ ./test.sh Directory: test/Baltimore Directory: test/Cherry Hill Directory: test/Edison Directory: test/New York City Directory: test/Philadelphia Directory: test/this is a dirname with quotes, lfs, escapes: "/''' '' /e/n/d $ ./test.sh "Cherry Hill" "New York City" Directory: Cherry Hill Directory: New York City


Para agregar a lo que dijo Jonathan : use la opción -print0 para find junto con xargs siguiente manera:

find test/* -type d -print0 | xargs -0 command

Eso ejecutará el comando command con los argumentos apropiados; los directorios con espacios en ellos se cotizarán correctamente (es decir, se pasarán como un solo argumento).


Para mí esto funciona, y es bastante "limpio":

for f in "$(find ./test -type d)" ; do echo "$f" done


Primero, no lo hagas de esa manera. El mejor enfoque es usar find -exec correctamente:

# this is safe find test -type d -exec echo ''{}'' +

El otro enfoque seguro es usar lista terminada en NUL, aunque esto requiere que su find soporte -print0 :

# this is safe while IFS= read -r -d '''' n; do printf ''%q/n'' "$n" done < <(find test -mindepth 1 -type d -print0)

También puede llenar una matriz desde find y pasar esa matriz más tarde:

# this is safe declare -a myarray while IFS= read -r -d '''' n; do myarray+=( "$n" ) done < <(find test -mindepth 1 -type d -print0) printf ''%q/n'' "${myarray[@]}" # printf is an example; use it however you want

Si su búsqueda no es compatible con -print0 , su resultado es inseguro - lo siguiente no se comportará como se desea si existen archivos que contengan nuevas líneas en sus nombres (que, sí, es legal):

# this is unsafe while IFS= read -r n; do printf ''%q/n'' "$n" done < <(find test -mindepth 1 -type d)

Si uno no va a usar uno de los anteriores, un tercer enfoque (menos eficiente en términos de uso de tiempo y memoria, ya que lee la salida completa del subproceso antes de dividir palabras) es usar una variable IFS que no contiene el caracter de espacio Desactivar globbing ( set -f ) para evitar cadenas que contengan caracteres glob como [] , * o ? de ser expandido:

# this is unsafe (but less unsafe than it would be without the following precautions) ( IFS=$''/n'' # split only on newlines set -f # disable globbing for n in $(find test -mindepth 1 -type d); do printf ''%q/n'' "$n" done )

Finalmente, para el caso del parámetro de la línea de comandos, debería usar matrices si su shell las admite (es decir, es ksh, bash o zsh):

# this is safe for d in "$@"; do printf ''%s/n'' "$d" done

mantendrá la separación. Tenga en cuenta que las citas (y el uso de $@ lugar de $* ) es importante. Las matrices también se pueden rellenar de otras formas, como las expresiones globales:

# this is safe entries=( test/* ) for d in "${entries[@]}"; do printf ''%s/n'' "$d" done


Puede usar IFS (separador de campo interno) usando temporalmente:

OLD_IFS=$IFS # Stores Default IFS IFS=$''/n'' # Set it to line break for f in `find test/* -type d`; do echo $f done $IFS=$OLD_IFS


Tuvo que lidiar con espacios en blanco en nombres de ruta, también. Lo que finalmente hice fue usar una recursión y for item in /path/* :

function recursedir { local item for item in "${1%/}"/* do if [ -d "$item" ] then recursedir "$item" else command fi done }


acabo de descubrir que hay algunas similitudes entre mi question y la tuya. Aparrently si quieres pasar argumentos a los comandos

test.sh "Cherry Hill" "New York City"

para imprimirlos en orden

for SOME_ARG in "$@" do echo "$SOME_ARG"; done;

observe que el $ @ está rodeado de comillas dobles, algunas notas here


ps si solo se trata de espacio en la entrada, algunas comillas dobles funcionaron sin problemas para mí ...

read artist; find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 ''{}'' /;



#!/bin/bash dirtys=() for folder in * do if [ -d "$folder" ]; then dirtys=("${dirtys[@]}" "$folder") fi done for dir in "${dirtys[@]}" do for file in "$dir"//*.mov # <== *.mov do #dir_e=`echo "$dir" | sed ''s/[[:space:]]//// /g''` -- This line will replace each space into ''/ '' out=`echo "$file" | sed ''s//(.*/)///(.*/)//2/''` # These two line code can be written in one line using multiple sed commands. out=`echo "$out" | sed ''s/[[:space:]]/_/g''` #echo "ffmpeg -i $out_e -sameq -vcodec msmpeg4v2 -acodec pcm_u8 $dir_e/${out/%mov/avi}" `ffmpeg -i "$file" -sameq -vcodec msmpeg4v2 -acodec pcm_u8 "$dir"/${out/%mov/avi}` done done

El código anterior convertirá los archivos .mov a .avi. Los archivos .mov están en diferentes carpetas y los nombres de las carpetas también tienen espacios en blanco . Mi script anterior convertirá los archivos .mov a .avi en la misma carpeta. No sé si les ayudará a los pueblos.

Caso:

[sony@localhost shell_tutorial]$ ls Chapter 01 - Introduction Chapter 02 - Your First Shell Script [sony@localhost shell_tutorial]$ cd Chapter/ 01/ -/ Introduction/ [sony@localhost Chapter 01 - Introduction]$ ls 0101 - About this Course.mov 0102 - Course Structure.mov [sony@localhost Chapter 01 - Introduction]$ ./above_script ... successfully executed. [sony@localhost Chapter 01 - Introduction]$ ls 0101_-_About_this_Course.avi 0102_-_Course_Structure.avi 0101 - About this Course.mov 0102 - Course Structure.mov [sony@localhost Chapter 01 - Introduction]$ CHEERS!

¡Aclamaciones!


find . -print0|while read -d $''/0'' file; do echo "$file"; done


find . -type d | while read file; do echo $file; done

Sin embargo, no funciona si el nombre de archivo contiene líneas nuevas. Lo anterior es la única solución que conozco cuando realmente quiere tener el nombre del directorio en una variable. Si solo quiere ejecutar algún comando, use xargs.

find . -type d -print0 | xargs -0 echo ''The directory is: ''


find Downloads -type f | while read file; do printf "%q/n" "$file"; done