Comment puis-je échapper à l'espace blanc dans une liste de boucles bash?
j'ai un script shell bash qui boucle tous les répertoires enfants (mais pas les fichiers) d'un certain répertoire. Le problème est que certains noms de répertoires contiennent des espaces.
voici le contenu de mon répertoire test:
$ls -F test
Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
Et le code qui parcourt les répertoires:
for f in `find test/* -type d`; do
echo $f
done
Voici la sortie:
test/Baltimore test/Cherry Hill test/Edison test/New York City test/Philadelphia
Cherry Hill et New York City sont traités que 2 ou 3 entrées séparées.
j'ai essayé de citer les noms de fichiers, comme ceci:
for f in `find test/* -type d | sed -e 's/^/"/' | sed -e 's/$/"/'`; do
echo $f
done
, mais en vain.
il doit y avoir un moyen simple de le faire.
Les réponses ci-dessous sont grands. Mais pour rendre cela plus compliqué - Je ne veux pas toujours utiliser les répertoires énumérés dans mon répertoire de test. Parfois je veux passer dans les noms de répertoire comme paramètres de ligne de commande plutôt.
J'ai suivi la suggestion de Charles de mettre en place les Fi et je suis arrivé à la conclusion suivante:
dirlist="${@}"
(
[[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'n'
for d in $dirlist; do
echo $d
done
)
et cela fonctionne très bien à moins qu'il y ait des espaces dans les arguments en ligne de commande (même si ces arguments sont cités). Par exemple, appeler le script comme ceci: test.sh "Cherry Hill" "New York City"
produit la sortie suivante:
Cherry Hill New York City
20 réponses
D'abord, ne le faites pas de cette façon. La meilleure approche est d'utiliser find -exec
correctement:
# this is safe
find test -type d -exec echo '{}' +
l'autre approche sûre est d'utiliser la liste nul-terminated, bien que cela nécessite que votre trouver le support -print0
:
# this is safe
while IFS= read -r -d '' n; do
printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)
vous pouvez aussi peupler un tableau de find, et passer ce tableau plus tard:
# 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 votre find ne supporte pas -print0
, votre résultat est alors dangereux -- le ci-dessous ne se comportera pas comme souhaité s'il existe des fichiers contenant des lignes nouvelles dans leur nom (ce qui, Oui, est légal):
# this is unsafe
while IFS= read -r n; do
printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)
si l'on ne va pas utiliser l'une des méthodes ci-dessus, une troisième approche (moins efficace en termes d'utilisation du temps et de la mémoire, car elle lit toute la sortie du sous-processus avant de faire du" word-splitting") est d'utiliser une variable IFS
qui ne contient pas le caractère d'espace. Désactiver le globbing ( set -f
) pour empêcher les cordes contenant du glob caractères tels que []
, *
ou ?
à partir d'être élargi:
# 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
)
enfin, pour le cas de paramètre en ligne de commande, vous devriez utiliser des tableaux si votre shell les supporte (c'est-à-dire qu'il s'agit de ksh, bash ou zsh):
# this is safe
for d in "$@"; do
printf '%s\n' "$d"
done
maintiendra la séparation. Notez que la citation (et l'utilisation de $@
plutôt que $*
) est importante. Les tableaux peuvent être remplis d'autres façons, comme glob expressions:
# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
printf '%s\n' "$d"
done
find . -type d | while read file; do echo $file; done
cependant, ne fonctionne pas si le nom de fichier contient des newlines. Ce qui précède est la seule solution que je connaisse lorsque vous voulez réellement avoir le nom du répertoire dans une variable. Si vous voulez juste exécuter une commande, utilisez xargs.
find . -type d -print0 | xargs -0 echo 'The directory is: '
Voici une solution simple qui gère les onglets et/ou les espaces dans le nom de fichier. Si vous devez traiter avec d'autres caractères étranges dans le nom de fichier comme newlines, choisissez une autre réponse.
Le répertoire de test
ls -F test
Baltimore/ Cherry Hill/ Edison/ New York City/ Philadelphia/ cities.txt
le code pour aller dans les répertoires
find test -type d | while read f ; do
echo "$f"
done
le nom du fichier doit être cité ( "$f"
) s'il est utilisé comme argument. Sans guillemets, les espaces agissent comme séparateur d'argument et multiple les arguments sont donnés à la commande appelée.
et la sortie:
test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia
c'est extrêmement délicat dans le standard Unix, et la plupart des solutions ne sont pas compatibles avec les nouvelles lignes ou un autre caractère. Cependant, si vous utilisez L'ensemble D'outils GNU, vous pouvez exploiter l'option find
et utiliser xargs
avec l'option correspondante -0
(moins-zéro). Il y a deux caractères qui ne peuvent pas apparaître dans un simple nom de fichier; Ce sont slash et NUL '\0'. Évidemment, slash apparaît dans pathnames, donc la solution GNU d'utiliser un NUL '\0 ' pour marquer à la fin du nom est ingénieux et à toute épreuve.
pourquoi ne pas simplement mettre
IFS='\n'
devant le commandement? Cela change le séparateur de champ de < Space> < Tab> < Newline> à juste < Newline>
j'utilise
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "" -type d ! -path "" )
do
echo $f
done
IFS=$SAVEIFS
ne suffirait-il pas?
Idée tirée de http://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
Ne stockez pas les listes comme des chaînes; stockez-les comme des tableaux pour éviter toute cette confusion de délimiteurs. Voici un exemple de script qui fonctionnera soit sur tous les sous-répertoires de test, soit sur la liste fournie sur sa ligne de commande:
#!/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
essayons maintenant sur un répertoire test avec une courbe ou deux lancée:
$ 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
find . -print0|while read -d $'"151900920"' file; do echo "$file"; done
ps s'il ne s'agit que d'espace dans l'entrée, alors quelques guillemets ont bien fonctionné pour moi...
read artist;
find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;
vous pouvez utiliser L'IFS (internal field separator) temporairement en utilisant:
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
pour ajouter à ce que Jonathan a dit: utiliser l'option -print0
pour find
en conjonction avec xargs
comme suit:
find test/* -type d -print0 | xargs -0 command
qui exécutera la commande command
avec les arguments appropriés; les répertoires avec des espaces y seront correctement Cités (c'est-à-dire qu'ils seront passés en un seul argument).
#!/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/\(.*\)\/\(.*\)//'` # 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
le code ci-dessus sera converti .mov file to .avi. Le. les fichiers mov sont dans différents dossiers et les noms de dossiers ont espaces blancs aussi. Mon script ci-dessus convertira le .mov file to .avi fichier dans le même dossier lui-même. Je ne sais pas si cela vous aidera peuples.
:
[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!
santé!
devait aussi s'occuper des espaces dans pathnames. Ce que j'ai finalement fait était d'utiliser une récursion et for item in /path/*
:
function recursedir {
local item
for item in "${1%/}"/*
do
if [ -d "$item" ]
then
recursedir "$item"
else
command
fi
done
}
convertissez la liste des fichiers en tableau de Bash. Cela utilise L'approche de Matt McClure pour retourner un tableau à partir D'une fonction de Bash: http://notes-matthewlmcclure.blogspot.com/2009/12/return-array-from-bash-function-v-2.html Le résultat est un moyen de convertir n'importe quelle entrée multi-ligne en un tableau 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
cette approche semble fonctionner même en présence de mauvais caractères, et est un moyen général de convertir n'importe quelle entrée en un tableau de Bash. Le l'inconvénient est que si L'entrée est longue, vous pouvez dépasser les limites de taille de la ligne de commande de Bash, ou utiliser de grandes quantités de mémoire.
approches où la boucle qui fonctionne éventuellement sur la liste ont également la liste piped in ont l'inconvénient que la lecture de stdin n'est pas facile (comme demander à l'utilisateur pour l'entrée), et la boucle est un nouveau processus de sorte que vous pouvez vous demander pourquoi les variables que vous mettez à l'intérieur de la boucle ne sont pas disponibles après la fin de la boucle.
I il n'aime pas non plus la configuration de si, il peut gâcher d'autres codes.
vient de découvrir qu'il y a des similitudes entre ma question et la vôtre. Aparrently si vous voulez passer des arguments dans des commandes
test.sh "Cherry Hill" "New York City"
pour les imprimer dans l'ordre
for SOME_ARG in "$@"
do
echo "$SOME_ARG";
done;
notez que le $@ est entouré de doubles citations, quelques notes ici
j'avais besoin du même concept pour compresser séquentiellement plusieurs répertoires ou fichiers à partir d'un certain dossier. J'ai résolu en utilisant awk pour parsel la liste de ls et pour éviter le problème de l'espace dans le nom.
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'en pensez-vous?
find Downloads -type f | while read file; do printf "%q\n" "$file"; done
Eh bien, je vois trop de réponses compliquées. Je ne veux pas passer la sortie de find utility ou écrire une boucle , parce que find a l'option "exec" pour cela.
mon problème était que je voulais déplacer tous les fichiers avec une extension dbf vers le dossier courant et certains d'entre eux contenaient de l'espace blanc.
j'ai abordé cela ainsi:
find . -name \*.dbf -print0 -exec mv '{}' . ';'
Semble beaucoup plus simple pour moi
pour moi cela fonctionne, et il est à peu près "propre":
for f in "$(find ./test -type d)" ; do
echo "$f"
done
vient d'avoir un simple problème de variante... Convertir des dossiers de dactylographié .flv .mp3 (bâillement).
for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done
rechercher récursivement tous les fichiers flash de L'utilisateur Macintosh et les transformer en audio (copier, Pas de transcode) ... c'est comme le tout ci-dessus, en notant que lire au lieu de Juste "Pour fichier dans
" s'échappera.