J'ai un script bash shell qui parcourt tous les répertoires enfants (mais pas les fichiers) d'un certain répertoire. Le problème est que certains noms de répertoire contiennent des espaces.
Voici le contenu de mon répertoire de 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/Nouveau York Ville test/Philadelphie
Cherry Hill et New York sont traitées comme 2 ou 3 entrées distinctes.
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 faire cela.
Les réponses ci-dessous sont excellentes. Mais pour compliquer les choses, je ne veux pas toujours utiliser les répertoires listés dans mon répertoire de test. Parfois, je veux plutôt passer les noms de répertoires en tant que paramètres de ligne de commande.
J'ai pris la suggestion de Charles de régler l'IFS et ai proposé ce qui suit:
dirlist="${@}"
(
[[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
for d in $dirlist; do
echo $d
done
)
et cela fonctionne parfaitement sauf s'il y a des espaces dans les arguments de la ligne de commande (même si ces arguments sont cités). Par exemple, appelez le script comme suit: test.sh "Cherry Hill" "New York City"
produit la sortie suivante:
Cherry Hill Nouveau York Ville
Tout d'abord, ne le faites pas de cette façon. La meilleure approche consiste à utiliser find -exec
correctement:
# this is safe
find test -type d -exec echo '{}' +
L’autre approche sûre consiste à utiliser la liste terminée par NUL, bien que cela nécessite que votre support de recherche trouve -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 également remplir un tableau à partir de find et le transmettre ultérieurement:
# 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 trouvaille ne supporte pas -print0
, votre résultat est alors dangereux - les éléments ci-dessous ne se comporteront pas comme souhaité si des fichiers contenant des nouvelles lignes dans leurs noms (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 vous n'utilisez pas l’un des éléments ci-dessus, une troisième approche (moins efficace en termes de temps et d’utilisation de la mémoire, car elle lit la sortie entière du sous-processus avant de procéder au fractionnement de Word) consiste à utiliser un IFS
variable qui ne contient pas le caractère d'espacement. Désactiver le globbing (set -f
) pour empêcher les chaînes contenant des caractères globaux tels que []
, *
ou ?
d'être étendu:
# 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 du paramètre de ligne de commande, vous devriez utiliser des tableaux si votre Shell les prend en charge (c'est-à-dire que c'est 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 important. Les tableaux peuvent également être peuplés d'autres manières, telles que les expressions globales:
# 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, cela ne fonctionne pas si le nom du fichier contient des nouvelles lignes. Ce qui précède est la seule solution que je connaisse lorsque vous souhaitez 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 tabulations et/ou les espaces dans le nom du fichier. Si vous devez traiter d'autres caractères étranges dans le nom de fichier, tels que des nouvelles lignes, 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"
) si utilisé comme argument. Sans guillemets, les espaces servent de séparateur d'arguments et plusieurs arguments sont attribués à la commande invoquée.
Et la sortie:
test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia
Ceci est extrêmement délicat sous Unix standard, et la plupart des solutions se basent sur les nouvelles lignes ou sur un autre personnage. Toutefois, si vous utilisez l'ensemble d'outils GNU, vous pouvez exploiter l'option find
-print0
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 nom de fichier simple; ce sont slash et NUL '\ 0'. De toute évidence, les barres obliques apparaissent dans les noms de chemins, de sorte que la solution GNU consistant à utiliser un NUL '\ 0' pour marquer la fin du nom est ingénieuse et infaillible.
Vous pouvez utiliser temporairement IFS (séparateur de champs interne) 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
Pourquoi ne pas simplement mettre
IFS='\n'
devant la commande pour? Cela change le séparateur de champ de <Space> <Tab> <Newline> à <Newline>.
J'utilise
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "$1" -type d ! -path "$1" )
do
echo $f
done
IFS=$SAVEIFS
Cela 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 sous forme de chaînes; stockez-les sous forme de tableaux pour éviter toute cette confusion de délimiteur. 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 ceci sur un répertoire de test avec une ou deux courbes insérées:
$ 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 $'\0' file; do echo "$file"; done
ps si ce n’est que de l’espace dans l’entrée, alors quelques guillemets doubles se sont bien déroulés pour moi ...
read artist;
find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;
Pour ajouter à quoi Jonathan dit: utilisez le -print0
option pour find
en conjonction avec xargs
comme suit:
find test/* -type d -print0 | xargs -0 command
Cela exécutera la commande command
avec les arguments appropriés; les répertoires contenant des espaces seront correctement cités (c'est-à-dire qu'ils seront passés sous la forme d'un argument).
Il fallait aussi traiter les espaces dans les noms de chemin. J'ai finalement utilisé une récursivité et for item in /path/*
:
function recursedir {
local item
for item in "${1%/}"/*
do
if [ -d "$item" ]
then
recursedir "$item"
else
command
fi
done
}
#!/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
Le code ci-dessus convertira les fichiers .mov en .avi. Les fichiers .mov sont dans des dossiers différents et les noms des dossiers ont espaces blancs aussi. Mon script ci-dessus convertira les fichiers .mov en fichiers .avi dans le même dossier. Je ne sais pas si cela vous aide les peuples.
Cas:
[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!
À votre santé!
Convertissez la liste de fichiers en tableau Bash. Ceci utilise l'approche de Matt McClure pour retourner un tableau à partir d'une fonction 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 multiligne 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 lorsque des caractères incorrects sont présents et constitue un moyen général de convertir toute entrée en tableau Bash. L'inconvénient est que si l'entrée est longue, vous pouvez dépasser les limites de taille de ligne de commande de Bash ou utiliser de grandes quantités de mémoire.
Les approches dans lesquelles la boucle qui fonctionne finalement sur la liste ont également été acheminées ont l'inconvénient que la lecture de stdin n'est pas facile (par exemple, demander à l'utilisateur d'entrer) et que la boucle est un nouveau processus. Vous vous demandez peut-être pourquoi les variables vous définissez à l'intérieur de la boucle ne sont pas disponibles après la fin de la boucle.
Je n'aime pas non plus définir IFS, cela peut gâcher un autre code.
je viens de découvrir qu'il existe certaines similitudes entre mon question et le vôtre. Apparemment si vous voulez passer des arguments dans des commandes
test.sh "Cherry Hill" "New York City"
les imprimer dans l'ordre
for SOME_ARG in "$@"
do
echo "$SOME_ARG";
done;
remarquez que le $ @ est entouré de guillemets, de notes ici
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 l'utilitaire find ou écrire une boucle, car find a l'option "exec" pour cela.
Mon problème était que je voulais déplacer tous les fichiers avec l'extension dbf dans le dossier actuel et que certains d'entre eux contenaient des espaces.
Je l'ai abordé ainsi:
find . -name \*.dbf -print0 -exec mv '{}' . ';'
Semble beaucoup simple pour moi
J'avais besoin du même concept pour compresser séquentiellement plusieurs répertoires ou fichiers d'un dossier donné. J'ai résolu l'utilisation d'awk pour sélectionner la liste de ls et é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'est-ce que tu penses?