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 ''{}'' /;
yo suelo
SAVEIFS=$IFS
IFS=$(echo -en "/n/b")
for f in $( find "$1" -type d ! -path "$1" )
do
echo $f
done
IFS=$SAVEIFS
¿No sería eso suficiente?
Idea tomada de http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
#!/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