bash: di chuyển tệp có dấu cách


10

Khi tôi di chuyển một tệp duy nhất có khoảng trắng trong tên tệp, nó sẽ hoạt động như sau:

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

Bây giờ tôi có một danh sách các tệp có thể chứa dấu cách và tôi muốn di chuyển chúng. Ví dụ:

$ 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

Tại sao ví dụ đầu tiên hoạt động, nhưng ví dụ thứ hai thì không? Làm thế nào tôi có thể làm cho nó hoạt động?

Câu trả lời:


20

Không bao giờ, không bao giờ sử dụngfor foo in $(cat bar) . Đây là một sai lầm kinh điển, thường được gọi là bash pit số 1 . Thay vào đó, bạn nên sử dụng:

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

Khi bạn chạy forvòng lặp, bash sẽ áp dụng wordsplitting với những gì nó đọc, có nghĩa là a strange blue cloudsẽ được đọc như a, strange, bluecloud:

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

So với:

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

Hoặc thậm chí, nếu bạn nhấn mạnh vào UUoC :

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

Vì vậy, whilevòng lặp sẽ đọc qua đầu vào của nó và sử dụng readđể gán mỗi dòng cho một biến. Bộ IFS=đặt dấu tách trường đầu vào thành NULL *-rtùy chọn readngăn không cho nó diễn giải các dấu gạch chéo ngược (do đó \tđược coi là dấu gạch chéo + tchứ không phải dưới dạng tab). Phần --sau mvcó nghĩa là "xử lý mọi thứ sau - như một đối số chứ không phải tùy chọn", cho phép bạn xử lý các tên tệp bắt đầu bằng -chính xác.


* Điều này không cần thiết ở đây, nói đúng ra, lợi ích duy nhất trong kịch bản này là không readxóa bất kỳ khoảng trắng hàng đầu hoặc dấu vết nào, nhưng đó là một thói quen tốt khi bạn cần xử lý tên tệp chứa các ký tự dòng mới, hoặc nói chung, khi bạn cần có khả năng xử lý các tên tệp tùy ý.


1
ok, tôi chỉ tìm cách thoát khỏi phần "$ file". không mong đợi lỗi ở một nơi khác.
MrJingles87

@ MrJingles87 Tôi biết. Điều này nhận được tất cả mọi người tại một số điểm. Sẽ rất không trực quan nếu bạn không biết về nó.
terdon

@JohnKugelman đúng vậy, điểm tốt. Trả lời chỉnh sửa, cảm ơn.
terdon

IFS=sẽ không giúp với dòng mới trong tên tập tin. Định dạng của tệp ở đây chỉ đơn giản là không cho phép tên tệp với dòng mới. IFS=là cần thiết cho tên tệp bắt đầu kết thúc trong không gian hoặc tab (hoặc bất kỳ ký tự nào ngoài dòng mới $ IFS có trước).
Stéphane Chazelas

Cạm bẫy hàng loạt đó là nhiều hơn về việc cố gắng phân tích đầu ra lshoặc findthay thế lệnh khi có những cách tốt hơn, đáng tin cậy hơn để làm điều đó. $(cat file)sẽ ổn khi làm đúng Thật khó để "làm điều đó đúng" với một vòng lặp đọc trong khi đó với vòng lặp + $ (...). Xem câu trả lời của tôi.
Stéphane Chazelas

11

Rằng $(cat file_list.txt)trong các vỏ POSIX như bashtrong ngữ cảnh danh sách là toán tử split + global ( zshchỉ thực hiện phần tách như bạn mong đợi).

Nó phân tách trên các ký tự của $IFS(theo mặc định, SPC , TAB và NL) và thực hiện toàn cầu trừ khi bạn tắt hoàn toàn toàn cầu.

Tại đây, bạn chỉ muốn phân chia trên dòng mới và không muốn phần toàn cầu, vì vậy nó phải là:

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

Điều đó cũng có lợi thế (qua một while readvòng lặp) để loại bỏ các dòng trống, bảo toàn một dòng cuối bị xóa và bảo toàn mvstdin (cần thiết trong trường hợp nhắc nhở chẳng hạn).

Mặc dù vậy, nó có nhược điểm là toàn bộ nội dung của tệp phải được lưu trong bộ nhớ (nhiều lần với các shell như bashzsh).

Với một số vỏ ( ksh, zshvà đến một mức độ thấp hơn bash), bạn có thể tối ưu hóa nó với $(<file_list.txt)thay vì $(cat file_list.txt).

Để thực hiện tương đương với một while readvòng lặp, bạn cần:

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

Hoặc với bash:

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

Hoặc với zsh:

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

Hoặc với GNU mvzsh:

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

Hoặc với GNU mvvà GNU xargsvà ksh / zsh / bash:

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

Đọc thêm về ý nghĩa của việc bỏ các phần mở rộng không được trích dẫn tại hàm ý Bảo mật của việc quên trích dẫn một biến trong shell bash / POSIX


Tuy nhiên, tôi nghĩ bạn nên thừa nhận rằng $(cat file_list.txt)đọc toàn bộ tệp vào bộ nhớ trước khi lặp, trong khi while read ...chỉ đọc một dòng tại một thời điểm. Đây có thể là một vấn đề cho các tập tin lớn.
chepner

@chepner. Điểm tốt. Thêm.
Stéphane Chazelas

1

Thay vì viết một kịch bản, bạn có thể sử dụng find ..

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

đối với trường hợp khi các tệp được đặt trong tệp thì bạn có thể thực hiện như sau ::

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

thứ hai không chắc chắn mặc dù ..

Chúc may mắn


3
Nếu bạn đang sử dụng find, bạn cũng có thể sử dụng findcho tất cả mọi thứ. Tại sao mang xargsvào nó? find -type f -iname '*.txt' -exec mv {} /folder/to/destination/.
terdon

xargs chỉ thao tác các kết quả tìm kiếm ở đây: -Tôi gán mọi kết quả cho một biến F trong khi 0 và print0 chỉ đơn giản là đảm bảo các tệp có khoảng trắng không bị thoát. và -0 ngăn xargs thoát khỏi kết quả. không chắc cách -exec cư xử với không gian ..
Noel Alex Makumuli

2
-execcư xử hoàn hảo với tên tệp tùy ý (bao gồm cả tên chứa khoảng trắng, dòng mới hoặc bất kỳ sự kỳ lạ nào khác). Tôi biết cách xargshoạt động, cảm ơn :) Tôi chỉ không thấy điểm gọi một lệnh riêng khi findcó thể thực hiện nó cho bạn. Không có gì sai với nó, chỉ là không hiệu quả.
terdon

@terdonyou đúng .. -exec {} / path / Destination hoạt động trơn tru .. tôi thích xargs hơn vì có thêm nhiều thứ tôi có thể làm với nó.
Noel Alex Makumuli

xargscũng có nhược điểm của stdinberberber ( mvcần cho lời nhắc của nó). Cũng là một vấn đề với giải pháp của @ terdon. Với GNU xargsvà shell với quy trình thay thế, có thể được giảm bớt bằngxargs -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

TẬP TIN =zenity --title "Pick a Movie" --file-selection

cho TẬP_TIN trong "$ {FILE [0]}"

làm omxplayer "$ {FILE [0]}" đã xong

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

! / thùng / bash

TẬP TIN =zenity --title "Pick a Song" --file-selection

cho TẬP_TIN trong "$ {FILE [0]}"

chơi "$ {FILE [0]}" xong

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

! / thùng / bash

TRỰC TIẾP =zenity --title "Pick a Album" --file-selection --directory

cho DIR trong "$ {DIR [0]}"

làm cd "$ {TRỰC TIẾP [0]}" && tìm. -type f -name ' .ogg' -o -name ' .mp3' | sắp xếp - đảo ngược-sắp xếp | trong khi đọc TRỰC TIẾP; chơi "$ DIR" xong

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

! / thùng / bash

TRỰC TIẾP =zenity --title "Pick a Album" --file-selection --directory

cho DIR trong "$ {DIR [0]}"

làm cd "$ {TRỰC TIẾP [0]}" && tìm. -type f -name ' .ogg' -o -name ' .mp3' | trong khi đọc TRỰC TIẾP; chơi "$ DIR" xong


Vui lòng đọc về markdown và áp dụng một số trang điểm cho câu trả lời của bạn. Và hãy giải thích tại sao điều này trả lời câu hỏi. VÀ KHÔNG NÊN !!!

Tôi có cảm giác câu trả lời này không thuộc về câu hỏi này. Nhưng nếu vậy, nó sẽ đi xa, để cài đặt rất nhiều phần mềm cho một vấn đề đơn giản như vậy.
MrJingles87
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.