bash: การย้ายไฟล์ที่มีช่องว่าง


10

เมื่อฉันย้ายไฟล์เดียวที่มีช่องว่างในชื่อไฟล์มันทำงานเช่นนี้:

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

ตอนนี้ฉันมีรายการไฟล์ที่อาจมีช่องว่างและฉันต้องการย้ายไฟล์ ตัวอย่างเช่น:

$ 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

ทำไมตัวอย่างแรกถึงใช้ได้ แต่อันที่สองไม่เหมาะ ฉันจะทำให้มันทำงานได้อย่างไร

คำตอบ:


20

ไม่เคยใช้for foo in $(cat bar)เลย นี่คือความผิดพลาดที่คลาสสิกเป็นที่รู้จักกันทั่วไปว่าเป็นจำนวนหลุมพรางทุบตี 1 คุณควรใช้:

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

เมื่อคุณเรียกใช้forห่วงทุบตีจะใช้ wordsplitting กับสิ่งที่มันอ่านหมายความว่าa strange blue cloudจะได้รับการอ่านa, strange, blueและcloud:

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

เปรียบเทียบกับ:

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

หรือแม้กระทั่งถ้าคุณยืนยันในUUoC :

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

ดังนั้นwhileลูปจะอ่านอินพุตของมันและใช้readเพื่อกำหนดแต่ละบรรทัดให้กับตัวแปร IFS=ชุดคั่นช่องใส่โมฆะ*และ-rตัวเลือกในการreadหยุดมันจากการตีความหนีทับขวา (เพื่อให้\tได้รับการปฏิบัติเป็นเฉือน + tและไม่เป็นแท็บ) The --after the mvmean "ปฏิบัติต่อทุกสิ่งหลังจาก - เป็นอาร์กิวเมนต์และไม่ใช่ตัวเลือก" ซึ่งช่วยให้คุณจัดการกับชื่อไฟล์ที่เริ่มต้นด้วย-อย่างถูกต้อง


* สิ่งนี้ไม่จำเป็นที่นี่การพูดอย่างเคร่งครัดประโยชน์เพียงอย่างเดียวในสถานการณ์นี้คือการป้องกันไม่ให้readมีช่องว่างนำหน้าหรือตามหลัง แต่มันก็เป็นนิสัยที่ดีที่จะเข้าไปหาเมื่อคุณต้องการจัดการกับชื่อไฟล์ที่มีอักขระขึ้นบรรทัดใหม่หรือ โดยทั่วไปเมื่อคุณจำเป็นต้องจัดการกับชื่อไฟล์โดยพลการ


1
ตกลงฉันแค่มองหาวิธีที่จะหลบหนีส่วน "$ file" ไม่ได้คาดหวังข้อผิดพลาดที่อื่น
MrJingles87

@ MrJingles87 ฉันรู้ อันนี้ทำให้ทุกคนในบางจุด มันไม่ง่ายถ้าคุณไม่รู้
terdon

@ JohnKugelman ใช่แน่นอนเป็นจุดที่ดี ตอบแก้ไขขอบคุณ
terdon

IFS=จะไม่ช่วยขึ้นบรรทัดใหม่ในชื่อไฟล์ รูปแบบของไฟล์ที่นี่ไม่อนุญาตให้ใช้ชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่ IFS=จำเป็นสำหรับชื่อไฟล์ที่เริ่มต้นด้วยการเว้นวรรคหรือแท็บ (หรืออักขระอื่นใดนอกเหนือจาก newline $ IFS ที่มีอยู่ก่อน)
Stéphane Chazelas

ข้อผิดพลาดแบทช์นั้นเป็นเรื่องเกี่ยวกับการพยายามวิเคราะห์เอาต์พุตlsหรือfindด้วยการแทนที่คำสั่งเมื่อมีวิธีที่ดีกว่าน่าเชื่อถือมากกว่า $(cat file)จะไม่เป็นไรเมื่อทำถูกต้อง มันเป็นเรื่องยากที่จะ "ทำถูกต้อง" ด้วยการวนซ้ำในขณะที่อ่านแบบเดียวกับที่ใช้สำหรับ + ​​$ (... ) ดูคำตอบของฉัน
Stéphane Chazelas

11

ว่า$(cat file_list.txt)ใน POSIX หอยเหมือนbashในบริบทรายการเป็นแยก + ผู้ประกอบการ glob ( zshเพียง แต่แยกส่วนที่คุณคาดหวัง)

มันแยกกับตัวละครของ$IFS(โดยค่าเริ่มต้นSPC , TAB และ NL) และจะทำแบบนั้นเว้นแต่ว่าคุณปิดการทำแบบเดียวกันทั้งหมด

ที่นี่คุณต้องการแยกบรรทัดใหม่เท่านั้นและไม่ต้องการส่วน glob ดังนั้นควร:

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 readวง) เพื่อยกเลิกบรรทัดว่างรักษาบรรทัดที่ไม่ถูกทำลายต่อท้ายและรักษาmvstdin ของ (จำเป็นในกรณีที่แจ้งให้เป็นต้น)

มันมีข้อเสียแม้ว่าเนื้อหาทั้งหมดของไฟล์จะต้องถูกเก็บไว้ในหน่วยความจำ (หลายครั้งด้วยเชลล์เช่นbashและzsh)

ด้วยเปลือกหอยบางคน ( ksh, zshและในระดับน้อยbash) คุณสามารถเพิ่มประสิทธิภาพของมันกับแทน$(<file_list.txt)$(cat file_list.txt)

ในการทำสิ่งที่เทียบเท่ากับwhile readลูปคุณจะต้อง:

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

หรือด้วยbash:

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

หรือด้วยzsh:

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

หรือกับ GNU mvและzsh:

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

หรือด้วย GNU mvและ GNU xargsและ ksh / zsh / bash:

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

อ่านเพิ่มเติมเกี่ยวกับความหมายของการไม่ขยายส่วนขยายที่ความหมายด้านความปลอดภัยของการลืมอ้างตัวแปรในเชลล์ bash / POSIX


ฉันคิดว่าคุณควรยอมรับว่ามัน$(cat file_list.txt)อ่านไฟล์ทั้งหมดในหน่วยความจำก่อนที่จะวนซ้ำในขณะที่while read ...อ่านเพียงหนึ่งบรรทัดในแต่ละครั้ง นี่อาจเป็นปัญหาสำหรับไฟล์ขนาดใหญ่
chepner

@chepner จุดดี. ที่เพิ่ม
Stéphane Chazelas

1

แทนที่จะเขียนสคริปต์คุณสามารถใช้ find ..

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

สำหรับกรณีที่ไฟล์อยู่ในไฟล์คุณสามารถทำสิ่งต่อไปนี้ ::

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

ที่สองไม่แน่ใจว่า ..

โชคดี


3
หากคุณกำลังใช้findงานคุณอาจใช้findทุกอย่างได้เช่นกัน นำxargsเข้ามาทำไม find -type f -iname '*.txt' -exec mv {} /folder/to/destination/.
terdon

xargs เพียงแค่จัดการผลลัพธ์ของการค้นหาที่นี่: -I จะกำหนดทุกผลลัพธ์ให้กับตัวแปร F ในขณะที่ 0 และ print0 เพียงแค่รับประกันว่าไฟล์ที่มีช่องว่างจะไม่หนีออกมา และ -0 ป้องกันไม่ให้ xargs หนีผลลัพธ์ ไม่แน่ใจว่า -exec ทำงานกับช่องว่างได้อย่างไร
Noel Alex Makumuli

2
-execทำงานได้อย่างสมบูรณ์แบบด้วยชื่อไฟล์โดยพลการ (รวมถึงที่มีช่องว่างการขึ้นบรรทัดใหม่หรือความแปลกประหลาดอื่น ๆ ) ฉันรู้ว่ามันxargsทำงานอย่างไรขอบคุณ :) ฉันแค่ไม่เห็นจุดที่เรียกคำสั่งแยกเมื่อfindสามารถทำเพื่อคุณแล้ว ไม่มีอะไรผิดปกติกับมันไม่มีประสิทธิภาพ
terdon

@terdonyou ถูกต้อง .. -exec {} / เส้นทาง / ปลายทางทำงานได้อย่างราบรื่น .. ฉันชอบ xargs เพราะมีสิ่งพิเศษมากกว่าที่ฉันสามารถทำได้
Noel Alex Makumuli

xargsนอกจากนี้ยังมีข้อเสียเปรียบของการอุดตัน stdin (ซึ่งmvจำเป็นต้องแจ้งให้) ยังมีปัญหากับวิธีแก้ปัญหาของ @ terdon ด้วย GNU xargsและเปลือกหอยที่มีการทดแทนกระบวนการสามารถบรรเทาได้ด้วยxargs -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 / ทุบตี

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

สำหรับไฟล์ใน "$ {FILE [0]}"

ทำ omxplayer "$ {FILE [0]}" เรียบร้อยแล้ว

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

! / bin / ทุบตี

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

สำหรับไฟล์ใน "$ {FILE [0]}"

เล่น "$ {FILE [0]}" เรียบร้อยแล้ว

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

! / bin / ทุบตี

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

สำหรับ DIR ใน "$ {DIR [0]}"

ทำ cd "$ {DIR [0]}" && find -type f -name ' .ogg' -o -name ' .mp3' | เรียงลำดับ - ในขณะที่อ่าน DIR; เล่น "$ DIR" เสร็จแล้ว

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

! / bin / ทุบตี

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

สำหรับ DIR ใน "$ {DIR [0]}"

ทำ cd "$ {DIR [0]}" && find -type f -name ' .ogg' -o -name ' .mp3' | ในขณะที่อ่าน DIR; เล่น "$ DIR" เสร็จแล้ว


โปรดอ่านเกี่ยวกับการ markdown และใช้การแต่งหน้ากับคำตอบของคุณ และโปรดอธิบายว่าทำไมสิ่งนี้ตอบคำถาม และไม่ควรตะโกน !!!

ฉันรู้สึกว่าคำตอบนี้ไม่ได้เป็นของคำถามนี้ แต่ถ้าเป็นเช่นนั้นจะไปไกลเพื่อติดตั้งซอฟต์แวร์มากสำหรับปัญหาง่าย ๆ
MrJingles87
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.