Bash: Dateien mit Leerzeichen verschieben


10

Wenn ich eine einzelne Datei mit Leerzeichen im Dateinamen verschiebe, funktioniert dies folgendermaßen:

$ mv "file with spaces.txt" "new_place/file with spaces.txt"

Jetzt habe ich eine Liste von Dateien, die Leerzeichen enthalten können, und ich möchte sie verschieben. Zum Beispiel:

$ echo "file with spaces.txt" > file_list.txt
$ for file in $(cat file_list.txt); do mv "$file" "new_place/$file"; done;

mv: cannot stat 'file': No such file or directory
mv: cannot stat 'with': No such file or directory
mv: cannot stat 'spaces.txt': No such file or directory

Warum funktioniert das erste Beispiel, das zweite nicht? Wie kann ich es zum Laufen bringen?

Antworten:


20

Niemals, niemals benutzenfor foo in $(cat bar) . Dies ist ein klassischer Fehler, der allgemein als Bash-Fallstricke Nummer 1 bekannt ist . Sie sollten stattdessen verwenden:

while IFS= read -r file; do mv -- "$file" "new_place/$file"; done < file_list.txt

Wenn Sie die laufen forSchleife, gelten bash wordsplitting zu dem, was es heißt, dass Sinn a strange blue cloudals gelesen werden a, strange, blueund cloud:

$ cat files 
a strange blue cloud.txt
$ for file in $(cat files); do echo "$file"; done
a
strange
blue
cloud.txt

Vergleichen mit:

$ while IFS= read -r file; do echo "$file"; done < files 
a strange blue cloud.txt

Oder sogar, wenn Sie auf der UUoC bestehen :

$ cat files | while IFS= read -r file; do echo "$file"; done
a strange blue cloud.txt

Die whileSchleife liest also ihre Eingabe und verwendet die read, um jede Zeile einer Variablen zuzuweisen. Das IFS=setzt das Eingabefeldtrennzeichen auf NULL * und die -rOption , die readInterpretation von Backslash-Escapezeichen zu verhindern (wird also \tals Schrägstrich + tund nicht als Tabulator behandelt). Das --Nachher mvbedeutet "Behandle alles nach dem - als Argument und nicht als Option", mit dem du mit Dateinamen umgehen kannst, die -richtig beginnen.


* Dies ist hier streng genommen nicht erforderlich. Der einzige Vorteil in diesem Szenario besteht darin, readdass keine führenden oder nachfolgenden Leerzeichen entfernt werden. Es ist jedoch eine gute Angewohnheit, wenn Sie sich mit Dateinamen befassen müssen, die Zeilenumbrüche enthalten, oder im Allgemeinen, wenn Sie in der Lage sein müssen, mit beliebigen Dateinamen umzugehen.


1
ok, ich habe nur nach Wegen gesucht, um dem Teil "$ file" zu entkommen. habe den Fehler nicht woanders erwartet.
MrJingles87

@ MrJingles87 Ich weiß. Diese bekommt man alle an einem gewissen Punkt. Es ist sehr unintuitiv, wenn Sie nichts davon wissen.
Terdon

@ JohnKugelman ja in der Tat, guter Punkt. Antwort bearbeitet, danke.
Terdon

IFS=hilft nicht bei Zeilenumbrüchen in Dateinamen. Das Format der Datei hier erlaubt einfach keine Dateinamen mit Zeilenumbrüchen. IFS=wird für Dateinamen benötigt, die mit Leerzeichen oder Tabulatoren enden (oder für andere Zeichen als Zeilenumbruch $ IFS, die zuvor enthalten waren).
Stéphane Chazelas

In dieser Batch-Falle geht es eher darum, die Ausgabe von lsoder findmit Befehlsersetzungen zu analysieren, wenn es bessere und zuverlässigere Möglichkeiten gibt. $(cat file)wäre in Ordnung, wenn es richtig gemacht würde. Es ist ungefähr so ​​schwierig, es mit einer while-Leseschleife "richtig zu machen" wie mit einer für + $ (...). Siehe meine Antwort.
Stéphane Chazelas

11

Das $(cat file_list.txt)in POSIX-Shells wie bashim Listenkontext ist der Split + Glob-Operator ( zshnur der Split- Teil, wie Sie es erwarten würden).

Es teilt sich in Zeichen von $IFS(standardmäßig SPC , TAB und NL) auf und führt Glob aus, es sei denn, Sie deaktivieren Globbing insgesamt.

Hier möchten Sie nur auf Newline teilen und möchten nicht den Glob-Teil, also sollte es sein:

IFS='
' # split on newline only
set -o noglob # disable globbing

for file in $(cat file_list.txt); do # split+glob
  mv -- "$file" "new_place/$file"
done

while readDies hat auch den Vorteil (über eine Schleife), leere Zeilen zu verwerfen, eine nachfolgende nicht abgeschlossene Zeile beizubehalten und den Standardwert beizubehalten mv(der beispielsweise bei Eingabeaufforderungen erforderlich ist).

Es hat jedoch den Nachteil, dass der gesamte Inhalt der Datei im Speicher gespeichert werden muss (mehrmals mit Shells wie bashund zsh).

Mit einigen Muscheln ( ksh, zshund in geringerem Maße bash), können Sie es mit Optimierung $(<file_list.txt)statt $(cat file_list.txt).

Um das Äquivalent mit einer while readSchleife zu machen, benötigen Sie:

while IFS= read <&3 -r file || [ -n "$file" ]; do
  {
    [ -n "$file" ] || mv -- "$file" "new_place/$file"
  } 3<&-
done 3< file_list.txt

Oder mit bash:

readarray -t files < file_list.txt &&
for file in "${files[@]}"
  [ -n "$file" ] || mv -- "$file" "new_place/$file"
done

Oder mit zsh:

for file in ${(f)"$(<file_list.txt)"}
  mv -- "$file" "new_place/$file"
done

Oder mit GNU mvund zsh:

mv -t -- new_place ${(f)"$(<file_list.txt)"}

Oder mit GNU mvund GNU xargsund ksh / zsh / bash:

xargs -rd '\n' -a <(grep . file_list.txt) mv -t -- new_place

Lesen Sie mehr darüber, was es bedeutet, Erweiterungen bei den Auswirkungen auf die Sicherheit nicht zu zitieren, wenn Sie vergessen, eine Variable in Bash / POSIX-Shells zu zitieren


Ich denke, Sie sollten jedoch anerkennen, dass $(cat file_list.txt)die gesamte Datei vor dem Iterieren in den Speicher while read ...gelesen wird , während jeweils nur eine Zeile gelesen wird. Dies kann ein Problem für große Dateien sein.
Chepner

@chepner. Guter Punkt. Hinzugefügt.
Stéphane Chazelas

1

Anstatt ein Skript zu schreiben, können Sie find .. verwenden.

 find -type f -iname \*.txt -print0 | xargs -IF -0 mv F /folder/to/destination/

Für den Fall, dass sich die Dateien in der Datei befinden, können Sie Folgendes tun:

cat file_with_spaces.txt | xargs -IF -0 mv F /folder/to/destination

der zweite allerdings nicht sicher ..

Viel Glück


3
Wenn Sie verwenden find, können Sie auch findfür alles verwenden. Warum hineinbringen xargs? find -type f -iname '*.txt' -exec mv {} /folder/to/destination/.
Terdon

xargs manipuliert einfach die Ergebnisse von find hier: -Ich ordne jedes Ergebnis einer Variablen F zu, während 0 und print0 einfach garantieren, dass Dateien mit Leerzeichen nicht maskiert werden. und -0 verhindert, dass xargs den Ergebnissen entgeht. Ich bin mir nicht sicher, wie sich -exec mit Leerzeichen verhält.
Noel Alex Makumuli

2
-execverhält sich perfekt mit beliebigen Dateinamen (einschließlich solcher, die Leerzeichen, Zeilenumbrüche oder andere Verrücktheiten enthalten). Ich weiß, wie es xargsfunktioniert, danke :) Ich sehe keinen Sinn darin, einen separaten Befehl aufzurufen, wenn finddies bereits für Sie erledigt werden kann. Daran ist nichts auszusetzen, nur ineffizient.
Terdon

@terdonyou haben recht .. -exec {} / path / destination funktioniert reibungslos .. ich bevorzuge xargs wegen mehr zusätzlicher Dinge, die ich damit machen kann.
Noel Alex Makumuli

xargshat auch den Nachteil, dass stdin (das mvfür seine Eingabeaufforderungen benötigt wird) überlistet wird. Auch ein Problem mit @ terdons Lösung. Mit GNU xargsund Schalen mit Prozesssubstitution kann mitxargs -0IF -a <(find...) mv F /dest
Stéphane Chazelas

-1
FIRST INSTALL 

apt-get install chromium-browser

apt-get install omxplayer

apt-get install terminator

apt-get install nano (if not already installed)

apt-get install zenity (if not already installed)

THEN CREATE A BASH SCRIPT OF ALL OF THESE PERSONALLY WRITTEN SCRIPTS. 

MAKE SURE THAT EVERY SHELL SCRIPT IS A .sh FILE ENDING EACH ONE OF THESE IS SCRIPTED TO OPEN UP A DIRECTORY FOR YOU TO FIND AND PICK WHAT YOU WANT. 

IT RUNS A BACKGROUND TERMINAL & ALLOWS YOU TO CONTROL THE SONG OR MOVIE.  

MOVIE KEYS ARE AS FOLLOWS; p or space bar for pause, q for quit, - & + for sound up and down, left arrow and right arrow for skipping forward and back. 

MUSIC PLAYER REALLY ONLY HAS A SKIP SONG AND THAT IS CTRL+C AND IF THAT IS THE LAST SONG OR ONLY SONG THEN IT SHUTS DOWN AND THE TERMINAL GOES AWAY.


****INSTRUCTIONS TO MAKE THE SCRIPTS****
- OPEN UP A TERMINAL 

- CD TO THE DIRECTORY YOU WANT THE SCRIPTS TO BE
cd /home/pi/Desktop/

- OPEN UP THE NANO EDITOR WITH THE TITLE OF SHELL YOU WANT
sudo nano Movie_Player.sh

- INSIDE NANO, TYPE OR COPY/PAST (REMEMBER THAT IN TERMINAL YOU NEED TO CTRL+SHIFT+V) THE SCRITP

- SAVE THE DATA WITH CTRL+O
ctrl+o

- HIT ENTER TO SAVE AS THAT FILE NAME OR DELETE THE FILE NAME THEN TYPE NEW ONE JUST MAKE SURE IT ENDS IN .sh THEN HIT ENTER

- NEXT YOU NEED TO SUDO CHMOD IT WITH +X TO MAKE IT CLICKABLE AS A BASH
sudo chmod +x Movie_Player.sh

- FINALLY RUN IT TO TEST EITHER BY DOUBLE CLICKING IT AND CHOOSING "EXECUTE IN TERMINAL" OR BY ./ IT IN TERMINAL
./Movie_Player.sh

YOU ARE NOW GOOD TO PICK A MOVIE OR SELECT A SONG OR ALBUM AND ENJOY!  




**** ALL OF THESE SCRIPTS ACCOUNT FOR SPACES IN THE FILENAME 
SO YOU CAN ACCESS "TOM PETTY" OR "SIXTEEN STONE" WITHOUT NEEDING THE " _ " BETWEEN THE WORDS.




-------WATCH A MOVIE SCRIPT ------- (

#! / bin / bash

DATEI =zenity --title "Pick a Movie" --file-selection

für DATEI in "$ {DATEI [0]}"

mache Omxplayer "$ {FILE [0]}" fertig

--------LISTEN TO A SONG -------

! / bin / bash

DATEI =zenity --title "Pick a Song" --file-selection

für DATEI in "$ {DATEI [0]}"

spiele "$ {FILE [0]}" fertig

--------LISTEN TO AN ALBUM ---------- SONGS IN ORDER

! / bin / bash

DIR =zenity --title "Pick a Album" --file-selection --directory

für DIR in "$ {DIR [0]}"

CD "$ {DIR [0]}" && finden. -type f -name ' .ogg' -o -name ' .mp3' | sort --version-sort | während DIR lesen; spiele "$ DIR" fertig fertig

--------LISTEN TO AN ALBUM WITH SONGS IN RANDOM ORDER --------

! / bin / bash

DIR =zenity --title "Pick a Album" --file-selection --directory

für DIR in "$ {DIR [0]}"

CD "$ {DIR [0]}" && finden. -type f -name ' .ogg' -o -name ' .mp3' | während DIR lesen; spiele "$ DIR" fertig fertig


Bitte lesen Sie über Abschriften und schminken Sie Ihre Antwort. Und bitte erklären Sie, warum dies die Frage beantwortet. UND NICHT SCHREIEN !!!

Ich habe das Gefühl, dass diese Antwort nicht zu dieser Frage gehört. Aber wenn ja, ist es viel zu weit, so viel Software für ein so einfaches Problem zu installieren.
MrJingles87
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.