Noms de fichiers avec des espaces brisés pour boucle, commande find

34

J'ai un script qui recherche dans tar tous les fichiers de plusieurs sous-dossiers et archives. Mon script est

for FILE in `find . -type f  -name '*.*'`
  do
if [[ ! -f archive.tar ]]; then

  tar -cpf archive.tar $FILE
else 
  tar -upf archive.tar $FILE 
fi
done

La commande find me donne la sortie suivante

find . -type f  -iname '*.*'
./F1/F1-2013-03-19 160413.csv
./F1/F1-2013-03-19 164411.csv
./F1-FAILED/F2/F1-2013-03-19 154412.csv
./F1-FAILED/F3/F1-2011-10-02 212910.csv
./F1-ARCHIVE/F1-2012-06-30 004408.csv
./F1-ARCHIVE/F1-2012-05-08 190408.csv

Mais la variable FILE ne stocke que la première partie du chemin ./F1/F1-2013-03-19 , puis la partie suivante 160413.csv .

J'ai essayé d'utiliser readavec une boucle while,

while read `find . -type f  -iname '*.*'`;   do ls $REPLY; done

mais j'obtiens l'erreur suivante

bash: read: `./F1/F1-2013-03-19': not a valid identifier

Quelqu'un peut-il suggérer un autre moyen?

Mise à jour

Comme suggéré dans les réponses ci-dessous, j'ai mis à jour les scripts

#!/bin/bash

INPUT_DIR=/usr/local/F1
cd $INPUT_DIR
for FILE in "$(find  . -type f -iname '*.*')"
do
archive=archive.tar

        if [ -f $archive ]; then
        tar uvf $archive "$FILE"
        else
        tar -cvf $archive "$FILE"
        fi
done

La sortie que je reçois est

./test.sh
tar: ./F1/F1-2013-03-19 160413.csv\n./F1/F1-2013-03-19 164411.csv\n./F1/F1-2013-03-19 153413.csv\n./F1/F1-2013-03-19 154412.csv\n./F1/F1-2012-09-10 113409.csv\n./F1/F1-2013-03-19 152411.csv\n./.tar\n./F1-FAILED/F3/F1-2013-03-19 154412.csv\n./F1-FAILED/F3/F1-2013-03-19 170411.csv\n./F1-FAILED/F3/F1-2012-09-10 113409.csv\n./F1-FAILED/F2/F1-2011-10-03 113911.csv\n./F1-FAILED/F2/F1-2011-10-02 165908.csv\n./F1-FAILED/F2/F1-2011-10-02 212910.csv\n./F1-ARCHIVE/F1-2012-06-30 004408.csv\n./F1-ARCHIVE/F1-2011-08-17 133905.csv\n./F1-ARCHIVE/F1-2012-10-21 154410.csv\n./F1-ARCHIVE/F1-2012-05-08 190408.csv: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
Ubuntuser
la source
4
Il semblerait que vous deviez définir IFS=$'\n'avant la boucle `for de le faire analyser chaque ligne
kiri

Réponses:

36

Utiliser foravec findest la mauvaise approche ici, voyez par exemple cet article sur la boîte de Pandore que vous ouvrez.

L'approche recommandée consiste à utiliser find, whileet readcomme décrit ici . Voici un exemple qui devrait fonctionner pour vous:

find . -type f -name '*.*' -print0 | 
while IFS= read -r -d '' file; do
    printf '%s\n' "$file"
done

De cette façon, vous délimitez les noms de fichiers avec des caractères null ( \0), cela signifie que la variation d'espace et d'autres caractères spéciaux ne poseront aucun problème.

Afin de mettre à jour une archive avec les fichiers findlocalisés, vous pouvez transmettre sa sortie directement à tar:

find . -type f -name '*.*' -printf '%p\0' | 
tar --null -uf archive.tar -T -

Notez que vous ne devez pas différencier si l'archive existe ou non, vous la tarmanipulerez avec discernement. Notez également l'utilisation de -printfici pour éviter d'inclure le ./bit dans l'archive.

Thor
la source
Merci, ça marche presque. La seule chose est son archivage en ./tant que goudron. ./.tar tar: ./archive.tar: file is the archive; not dumped
Ubuntuser
@ Ubuntuser Vous pouvez ajouter un simple contrôle pour voirif [[ "$FILE" == "./" ]]; then continue
kiri
@ Ubuntuser: Vous pouvez éviter le ./bit avec -printfvoir la réponse mise à jour. Cependant, cela ne devrait faire aucune différence s'il est inclus ou non, car il ne fait que référencer le répertoire actuel. J'ai également inclus une find/tarcombinaison alternative que vous pouvez utiliser.
Thor
Pour ceux d'entre vous qui veulent sortles résultats avant de les itérer, vous aurez besoin sort -zdu séparateur null.
Adambean
13

Essayez de citer la forboucle comme ceci:

for FILE in "`find . -type f  -name '*.*'`"   # note the quotation marks

Sans guillemets, bash ne gère pas bien les espaces et les nouvelles lignes ( \n) ...

Essayez aussi de mettre

IFS=$'\n'
kiri
la source
1
+1 pour $ IFS. Cela décrit le caractère séparateur.
Ray
1
C'est la solution qui a fonctionné pour moi. J'utilisais commpour comparer des listes de fichiers triés et les noms de fichiers contenaient des espaces, bien que citant des variables cela ne fonctionnait pas. Ensuite, j'ai vu cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html et la solution consistant à définir $ IFS avec IFS = $ (echo -en "\ n \ b") fonctionnait pour moi.
pbhj
Ajout des guillemets, élégant, simple, beau - merci!
Big Rich
8

Cela fonctionne et est plus simple:

find . -name '<pattern>' | while read LINE; do echo "$LINE" ; done

Nous remercions Rupa ( https://github.com/rupa/z ) pour cette réponse.

ShawnMilo
la source
4

En plus des citations appropriées, vous pouvez findutiliser un séparateur NULL, puis lire et traiter les résultats dans une whileboucle.

while read -rd $'\0' file; do
    something with "$file"
done < <(find  . -type f -name '*.*' -print0)

Cela devrait gérer tous les noms de fichiers compatibles POSIX - voir man find

   -print0
          True; print the full file name on the standard output, followed by a null character (instead of the newline character that  -print  uses).   This  allows  file
          names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output.  This option corresponds to the
          -0 option of xargs.
Steeldriver
la source
c'est la seule solution qui a fonctionné pour moi. Merci.
codefreak
1
find . <find arguments> -print0 | xargs -0 grep <pattern>
utilisateur2802945
la source
1

J'ai fait quelque chose comme ça pour trouver des fichiers pouvant contenir des espaces.

IFS=$'\n'
for FILE in `/usr/bin/find $DST/shared -name *.nsf | grep -v bookmark.nsf | grep -v names.nsf`; do
    file $FILE | tee -a $LOG
done

Travaillé comme un charme :)

Scott B
la source
0

Je pense que vous serez peut-être mieux d'utiliser findl'option -exec de.

find . -type f -name '*.*' -exec tar -cpf archive.tar {} +

Find exécute ensuite la commande à l'aide d'un appel système, de sorte que les espaces et les nouvelles lignes soient préservés (plutôt un tuyau qui nécessiterait la citation de caractères spéciaux). Notez que "tar -c" fonctionne que l'archive existe ou non, et que (du moins avec bash) ni {} ni + ne doivent être cités.

Drake Clarris
la source
-1

Comme suggéré par minerz029, vous devez citer l’extension de la findcommande. Vous devez également citer toutes les substitutions de $FILEdans votre boucle.

for FILE in "$(find . -type f  -name '*.*')"
do
    if [ ! -f archive.tar ]; then
        tar -cpf archive.tar "$FILE"
    else 
        tar -upf archive.tar "$FILE" 
    fi
done

Notez que la $()syntaxe devrait être préférée à l'utilisation de backticks; voir cette question U & L . J'ai également supprimé le [[mot clé et l' ai remplacé par la [commande car il s'agit de POSIX.

Joseph R.
la source
À propos de [[et [, il semble que ce [[soit plus récent et supporte plus de fonctionnalités telles que globbing et correspondance régulière. [[est seulement dans bash, cependant, passh
kiri
@ minerz029 Oui. C'est ce que je dis. Je ne sais pas ce que vous entendez par [[soutient globbing. Selon le wiki de Greg , aucun déplacement ne se produit à l'intérieur [[.
Joseph R.
Essayez [ "ab" == a? ] && echo "true"alors[[ "ab" == a? ]] && echo "true"
kiri
@ minerz029 Ce n'est pas globbing. Ce sont des expressions régulières (interprétées de manière lâche). Ce n'est pas un glob parce que a*signifie "a suivi d'un nombre quelconque de caractères" plutôt que "tous les fichiers dont le nom commence par aun nombre quelconque de caractères après". Essayez [ ab = a* ] && echo true contre [[ ab == a* ]] && echo true.
Joseph R.
Eh bien, fait [[toujours des expressions régulières tandis que [ne le fait pas. Doit avoir eu confus
kiri