Frage

Ich habe einen Bash-Shell-Skript, das durch alle Unterverzeichnisse (aber nicht Dateien) von einem bestimmten Verzeichnis-Schleifen. Das Problem ist, dass einige der Verzeichnisnamen Leerzeichen enthalten.

Hier ist der Inhalt meines Testverzeichnis:

$ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

Und der Code, der die Verzeichnisse Schleifen durch:

for f in `find test/* -type d`; do
  echo $f
done

Hier ist der Ausgang:

test/Baltimore
test/Cherry
Hill
test/Edison 
test/New
York
City
test/Philadelphia

Cherry Hill und New York City werden behandelt, als 2 oder 3 separate Einträge.

Ich habe versucht, die Dateinamen zu zitieren, etwa so:

for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
  echo $f
done

, aber ohne Erfolg.

Es muss eine einfache Möglichkeit, dies zu tun.


Die Antworten unten sind groß. Aber um dies komplizierter - ich weiß nicht immer die Verzeichnisse in meinem Test-Verzeichnis aufgeführt verwenden möchten. Manchmal mag ich als Befehlszeilenparameter stattdessen in den Verzeichnisnamen zu übergeben.

Ich nahm Charles' Vorschlag, die IFS Einstellung und kam mit dem folgenden:

dirlist="${@}"
(
  [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
  for d in $dirlist; do
    echo $d
  done
)

und das funktioniert ganz gut, es sei denn es Räume in den Kommandozeilenargumente sind (auch wenn diese Argumente notiert sind). Um zum Beispiel das Skript wie folgt aufrufen: test.sh "Cherry Hill" "New York City" erzeugt die folgende Ausgabe:

Cherry
Hill
New
York
City
War es hilfreich?

Lösung

Als erstes tun es nicht auf diese Weise. Der beste Ansatz ist find -exec richtig zu verwenden:

# this is safe
find test -type d -exec echo '{}' +

Die andere sichere Methode ist NUL-terminierte Liste zu verwenden, obwohl dies erfordert, dass Ihre Unterstützung finden -print0:

# this is safe
while IFS= read -r -d '' n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)

Sie können auch eine Reihe von Fund, bevölkern und das Array passieren später:

# 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

Wenn Ihr Fund -print0 nicht unterstützt, Ihr Ergebnis dann nicht sicher ist - die unten nicht so tun, als gewünscht, wenn Dateien mit Zeilenumbrüchen in ihrem Namen existieren (die, ja, das ist legal):

# this is unsafe
while IFS= read -r n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)

Wenn man geht, nicht eine der oben genannten verwenden, einen dritten Ansatz (weniger effizient sowohl in Bezug auf Zeit und Speichernutzung, da sie die gesamte Ausgabe des subprocess liest vor Wortspalt tun) ist ein verwenden, IFS Variable, die nicht das Leerzeichen enthält. Schalten Sie Globbing (set -f) zu verhindern Zeichenketten enthalten glob Zeichen wie [], * oder ? aus wird erweitert:

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

Schließlich wird für die Befehlszeilenparameter Fall sollten Sie mit Arrays, wenn die Shell sie unterstützt (das heißt es ist ksh, bash oder zsh):

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done

wird Trennung aufrechtzuerhalten. Beachten Sie, dass die zitierte (und die Verwendung von $@ statt $*) wichtig ist. Arrays können auch, wie glob Ausdrücke auf andere Weise gefüllt werden:

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
  printf '%s\n' "$d"
done

Andere Tipps

find . -type d | while read file; do echo $file; done

Allerdings funktioniert nicht, wenn die Dateinamen Zeilenumbrüche enthalten. Die oben ist die einzige Lösung, die ich kenne, wenn Sie wirklich wollen, die Verzeichnisnamen in einer Variablen haben. Wenn Sie nur einen Befehl ausführen möchten, verwenden Sie xargs.

find . -type d -print0 | xargs -0 echo 'The directory is: '

Hier ist eine einfache Lösung, die Laschen und / oder Leerzeichen im Dateinamen behandelt. Wenn Sie mit anderen seltsamen Zeichen im Dateinamen wie Zeilenumbrüche beschäftigen, eine andere Antwort wählen.

Das Testverzeichnis

ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

Der Code in die Verzeichnisse gehen

find test -type d | while read f ; do
  echo "$f"
done

Der Dateiname muss angegeben ("$f") werden, wenn als Argument verwendet. Ohne Anführungszeichen wirken die Räume als Argument Separator und mehr Argumente an den aufgerufenen Befehl gegeben.

Und die Ausgabe:

test/Baltimore
test/Cherry Hill
test/Edison
test/New York City
test/Philadelphia
Diese

ist äußerst heikel in Standard-Unix und die meisten Lösungen laufen Foul von Zeilenumbrüchen oder einem anderen Charakter. wenn Sie das GNU-Werkzeug-Set jedoch verwenden, dann können Sie die Option find -print0 nutzen und verwenden xargs mit der entsprechenden Option -0 (minus-Null). Es gibt zwei Zeichen, die nicht in einem einfachen Dateinamen angezeigt werden können; das ist Slash und '\ 0' NUL. Offensichtlich Strich erscheint in Pfadnamen, so dass die GNU-Lösung unter Verwendung eines NUL ‚\ 0‘ das Ende des Namen zu markieren ist genial und narrensicher.

Warum nicht einfach setzen

IFS='\n'

vor dem für den Befehl? Dadurch ändert sich die Feldtrenn von nur

find . -print0|while read -d $'\0' file; do echo "$file"; done

ich

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in $( find "$1" -type d ! -path "$1" )
do
  echo $f
done
IFS=$SAVEIFS

Wäre das nicht genug?
Idee genommen von http://www.cyberciti.biz/ Tipps / Handhabung-Dateinamen-mit-Räume-in-bash.html

Speichern Sie keine Listen als Strings; speichern sie als Arrays alle diese Trennzeichen um Verwirrung zu vermeiden. Hier ist ein Beispiel-Skript, das entweder für alle Unterverzeichnisse von Test betreiben werden, oder die Liste auf ihrer Befehlszeile angegeben:

#!/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

Lassen Sie sich dies nun versuchen, auf einem Testverzeichnis mit einer Kurve oder zwei geworfen:

$ 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

Sie können IFS verwenden (interne Feldtrenn) zeitlich mit:

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

ps wenn es nur über den Raum in den eingegeben wird, dann einige doppelten Anführungszeichen arbeiteten für mich glatt ...

read artist;

find "/mnt/2tb_USB_hard_disc/p_music/$artist" -type f -name *.mp3 -exec mpg123 '{}' \;

, um hinzuzufügen, was Jonathan sagte: die -print0 Option für find in Verbindung mit xargs wie folgt verwenden:

find test/* -type d -print0 | xargs -0 command

Das wird den Befehl command mit den richtigen Argumenten auszuführen; Verzeichnisse mit Leerzeichen in ihnen werden richtig zitiert werden (das heißt, sie als ein Argument übergeben würde).

#!/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

Der obige Code konvertiert MOV-Dateien .avi. Die .mov-Dateien sind in verschiedenen Ordnern und die Ordnernamen haben White Spaces zu. Mein obiger Skript der MOV-Dateien konvertieren Datei im selben Ordner selbst .avi. Ich weiß nicht, ob es ihre Völkern helfen.

Case:

[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!

Cheers!

Hat mit Leerzeichen in Pfadnamen zu tun hat, auch. Was schließlich ich tat, war eine Rekursion und for item in /path/* mit:

function recursedir {
    local item
    for item in "${1%/}"/*
    do
        if [ -d "$item" ]
        then
            recursedir "$item"
        else
            command
        fi
    done
}

Konvertieren Sie die Dateiliste in ein Bash-Array. Dies nutzt Matt McClure Ansatz für ein Array aus einer Bash-Funktion Rückkehr: http: // Notes-matthewlmcclure .blogspot.com / 2009/12 / Rückkehr-array-from-schlag-function-v-2.html Das Ergebnis ist ein Weg, um jeden mehrzeiligen Eingang mit einer Schlag-Array zu konvertieren.

#!/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

Dieser Ansatz scheint zu arbeiten, selbst wenn schlechte Zeichen vorhanden sind, und ist ein allgemeiner Weg, um jede Eingabe in einer Bash-Array zu konvertieren. Der Nachteil ist, wenn der Eingang lang Sie Bash Kommandozeilengröße Grenzen überschreiten könnte, oder große Mengen an Speicher aufbrauchen.

Anflüge, bei denen die Schleife, die auf der Liste schließlich funktioniert hat auch die Liste geleitet hat in den Nachteil, dass das Lesen stdin nicht einfach ist (wie der Benutzer zur Eingabe zu fragen) und die Schleife ist ein neues Verfahren, so dass Sie sein können fragen, warum Variablen, die Sie innerhalb der Schleife gesetzt nicht verfügbar, nachdem die Schleife beendet ist.

Ich mag nicht auch IFS Einstellung, kann es andere Codes vermasseln.

habe gerade herausgefunden es einige Ähnlichkeiten zwischen meiner Frage sind und deine. Aparrently wenn Sie Argumente in Befehle zu übergeben

test.sh "Cherry Hill" "New York City"

sie um auszudrucken

for SOME_ARG in "$@"
do
    echo "$SOME_ARG";
done;

die $ @ bemerken durch doppelte Anführungszeichen umgeben ist, ein paar Notizen hier

brauchte ich das gleiche Konzept nacheinander mehrere Verzeichnisse oder Dateien aus einem bestimmten Ordner zu komprimieren. Ich habe mit awk gelöst, um die Liste von ls parsel und das Problem des Leerraumes im Namen zu vermeiden.

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!!!"

Was denken Sie?

find Downloads -type f | while read file; do printf "%q\n" "$file"; done

Nun, ich sehe zu viele komplizierte Antworten. Ich möchte nicht die Ausgabe findet Anwendung weitergeben oder eine Schleife zu schreiben, da find „exec“ Option für diese hat.

Mein Problem war, dass ich alle Dateien mit DBF-Erweiterung auf den aktuellen Ordner und einige von ihnen enthaltenen weißen Raum bewegen wollte.

ich in Angriff genommen es so:

 find . -name \*.dbf -print0 -exec mv '{}'  . ';'

Sieht viel einfacher für mich

Für mich funktioniert, und es ist ziemlich viel „sauber“:

for f in "$(find ./test -type d)" ; do
  echo "$f"
done

hatte nur eine einfache Variante Problem ... Konvertieren von Dateien von typisierten .flv .mp3 (gähn).

for file in read `find . *.flv`; do ffmpeg -i ${file} -acodec copy ${file}.mp3;done

rekursiv alle Benutzer Flash-Dateien Macintosh finden und sie in Audio (Kopie, kein Transcodierungs) ... es ist wie die während oben, das ist für Datei in 'statt nur unter Hinweis darauf, lesen entkommen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top