GNU/Linux >> Linux Esercitazione >  >> Linux

Scorrere i file con spazi nei nomi??

Questa domanda ha già risposte qui :Perché il loop over dell'output di find è una cattiva pratica?

(8 risposte)
Chiuso 3 anni fa.

Ho scritto il seguente script per differenziare gli output di due directory con tutti gli stessi file in quanto tali:

#!/bin/bash

for file in `find . -name "*.csv"`  
do
     echo "file = $file";
     diff $file /some/other/path/$file;
     read char;
done

So che ci sono altri modi per raggiungere questo obiettivo. Curiosamente, però, questo script non riesce quando i file contengono spazi. Come posso affrontarlo?

Esempio di output di trova:

./zQuery - abc - Do Not Prompt for Date.csv

Risposta accettata:

Risposta breve (la più vicina alla tua risposta, ma gestisce gli spazi)

OIFS="$IFS"
IFS=$'n'
for file in `find . -type f -name "*.csv"`  
do
     echo "file = $file"
     diff "$file" "/some/other/path/$file"
     read line
done
IFS="$OIFS"

Risposta migliore (gestisce anche caratteri jolly e newline nei nomi dei file)

find . -type f -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

Miglior risposta (basata sulla risposta di Gilles)

find . -type f -name '*.csv' -exec sh -c '
  file="$0"
  echo "$file"
  diff "$file" "/some/other/path/$file"
  read line </dev/tty
' exec-sh {} ';'

O ancora meglio, per evitare di eseguire un sh per file:

find . -type f -name '*.csv' -exec sh -c '
  for file do
    echo "$file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
  done
' exec-sh {} +

Risposta lunga

Hai tre problemi:

  1. Per impostazione predefinita, la shell suddivide l'output di un comando in spazi, tabulazioni e nuove righe
  2. I nomi dei file potrebbero contenere caratteri jolly che verrebbero espansi
  3. Cosa succede se esiste una directory il cui nome termina con *.csv ?

1. Divisione solo su nuove righe

Per capire cosa impostare file a, la shell deve prendere l'output di find e interpretarlo in qualche modo, altrimenti file sarebbe solo l'intero output di find .

La shell legge IFS variabile, che è impostata su <space><tab><newline> per impostazione predefinita.

Quindi esamina ogni carattere nell'output di find . Non appena vede un carattere che si trova in IFS , pensa che segni la fine del nome del file, quindi imposta file a tutti i personaggi che ha visto fino ad ora e esegue il ciclo. Quindi inizia da dove era stato interrotto per ottenere il nome del file successivo ed esegue il ciclo successivo, ecc., fino a raggiungere la fine dell'output.

Quindi sta effettivamente facendo questo:

for file in "zquery" "-" "abc" ...

Per dirgli di dividere l'input solo su newline, devi farlo

IFS=$'n'

prima del tuo for ... find comando.

Ciò imposta IFS su una singola nuova riga, quindi si divide solo sulle nuove righe e non anche su spazi e tabulazioni.

Se stai usando sh o dash invece di ksh93 , bash o zsh , devi scrivere IFS=$'n' così invece:

IFS='
'

Probabilmente è abbastanza per far funzionare il tuo script, ma se sei interessato a gestire correttamente alcuni altri casi d'angolo, continua a leggere...

2. Espansione di $file senza caratteri jolly

All'interno del ciclo in cui lo fai

diff $file /some/other/path/$file

la shell tenta di espandere $file (di nuovo!).

Potrebbe contenere spazi, ma poiché abbiamo già impostato IFS sopra, non sarà un problema qui.

Ma potrebbe anche contenere caratteri jolly come * o ? , che porterebbe a comportamenti imprevedibili. (Grazie a Gilles per averlo fatto notare.)

Per dire alla shell di non espandere i caratteri jolly, metti la variabile tra virgolette doppie, ad es.

diff "$file" "/some/other/path/$file"

Lo stesso problema potrebbe morderci anche noi

for file in `find . -name "*.csv"`

Ad esempio, se avessi questi tre file

file1.csv
file2.csv
*.csv

(molto improbabile, ma comunque possibile)

Correlati:se cambio le autorizzazioni su un file tar, si applicherà ai file al suo interno?

Sarebbe come se avessi corso

for file in file1.csv file2.csv *.csv

che verrà ampliato a

for file in file1.csv file2.csv *.csv file1.csv file2.csv

causando file1.csv e file2.csv da elaborare due volte.

Invece, dobbiamo fare

find . -name "*.csv" -print | while IFS= read -r file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read line </dev/tty
done

read legge le righe dallo standard input, divide la riga in parole secondo IFS e li memorizza nei nomi delle variabili specificati.

Qui, gli stiamo dicendo di non dividere la riga in parole e di memorizzare la riga in $file .

Nota anche che read line è cambiato in read line </dev/tty .

Questo perché all'interno del ciclo, l'input standard proviene da find tramite la pipeline.

Se avessimo appena read , consumerebbe parte o tutto il nome di un file e alcuni file verrebbero saltati.

/dev/tty è il terminale da cui l'utente esegue lo script. Nota che ciò causerà un errore se lo script viene eseguito tramite cron, ma presumo che non sia importante in questo caso.

Quindi, cosa succede se il nome di un file contiene nuove righe?

Possiamo gestirlo modificando -print a -print0 e usando read -d '' alla fine di una pipeline:

find . -name "*.csv" -print0 | while IFS= read -r -d '' file; do
    echo "file = $file"
    diff "$file" "/some/other/path/$file"
    read char </dev/tty
done

Questo fa find metti un byte nullo alla fine di ogni nome di file. I byte nulli sono gli unici caratteri non consentiti nei nomi di file, quindi questo dovrebbe gestire tutti i possibili nomi di file, non importa quanto strani.

Per ottenere il nome del file sull'altro lato, utilizziamo IFS= read -r -d '' .

Dove abbiamo usato read sopra, abbiamo usato il delimitatore di riga predefinito di newline, ma ora, find utilizza null come delimitatore di riga. In bash , non puoi passare un carattere NUL in un argomento a un comando (anche quelli integrati), ma bash comprende -d '' nel senso di delimitato NUL . Quindi usiamo -d '' per far read usa lo stesso delimitatore di riga di find . Nota che -d $'

Linux
  1. Copia i file nel terminale Linux

  2. Linux - Sostituzione degli spazi nei nomi dei file

  3. Itera su un elenco di file con spazi

  4. Usando il carattere di sottolineatura nei nomi dei file?

  5. AWK e nomi di file con spazio al suo interno.

Come trovare file con il comando fd in Linux

Un modo semplice per unire i file con il comando Cat

Manipolazione di file con il File Manager del Pannello di controllo di Plesk

Come affrontare i nomi dei file con gli spazi in Linux

Come trovare file con dozzine di criteri con il comando Trova Bash

Proteggi Linux con il file Sudoers