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
110
demandé sur John Bachir 2008-11-19 08:10:42

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
100
répondu Charles Duffy 2015-06-27 16:29:27
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: '
25
répondu Johannes Schaub - litb 2008-11-19 05:50:24

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
20
répondu cbliard 2013-07-23 11:36:29

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.

7
répondu Jonathan Leffler 2008-11-19 05:45:43

pourquoi ne pas simplement mettre

IFS='\n'

devant le commandement? Cela change le séparateur de champ de < Space> < Tab> < Newline> à juste < Newline>

4
répondu oshunluvr 2012-02-26 11:07:26

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

4
répondu murpel 2012-06-10 14:26:06

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
4
répondu Gordon Davisson 2013-07-21 21:50:22
find . -print0|while read -d $'"151900920"' file; do echo "$file"; done
3
répondu Freakus 2012-03-15 17:45:46

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 '{}' \;
3
répondu hardbutnot 2012-11-03 23:48:30

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

3
répondu amazingthere 2017-01-09 23:16:00

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).

2
répondu Adam Rosenfield 2017-05-23 12:10:36
#!/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é!

1
répondu Sony George 2010-11-18 12:21:19

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
}
1
répondu Gilles 2012-02-24 17:46:38

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.

1
répondu Steve Zobell 2012-09-26 15:09:50

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?

0
répondu Hìr0 2013-08-24 19:47:17
find Downloads -type f | while read file; do printf "%q\n" "$file"; done
0
répondu Johan Kasselman 2016-01-15 09:57:38

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

0
répondu Tebe 2016-04-11 12:54:30

pour moi cela fonctionne, et il est à peu près "propre":

for f in "$(find ./test -type d)" ; do
  echo "$f"
done
-3
répondu AndrzejP 2011-09-19 17:28:21

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.

-4
répondu mark washeim 2010-04-21 19:19:28