Wie kann ich Leerraum in einer Bash-Schleife Liste entkommen?
-
08-07-2019 - |
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
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
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
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.