bash: movendo arquivos com espaços

9

Quando eu movo um único arquivo com espaços no nome do arquivo, funciona assim:

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

Agora tenho uma lista de arquivos que podem conter espaços e quero movê-los. Por exemplo:

$ 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

Por que o primeiro exemplo funciona, mas o segundo não funciona? Como posso fazer isso funcionar?

    
por MrJingles87 15.09.2017 / 11:12

4 respostas

19

Nunca use for foo in $(cat bar) . Este é um erro clássico, comumente conhecido como pitfall número 1 . Você deve usar:

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

Quando você executar o loop for , o bash aplicará o wordsplit ao que lê, o que significa que a strange blue cloud será lido como a , strange , blue e cloud :

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

Compare com:

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

Ou mesmo, se você insistir na UUoC :

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

Portanto, o loop while lerá sua entrada e usará o read para atribuir cada linha a uma variável. O IFS= define o separador do campo de entrada como NULL * e a opção -r de read impede que ele interprete escapes de barra invertida (para que \t seja tratado como barra + t e não como uma guia). O -- após o mv significa "tratar tudo após o - como um argumento e não como uma opção", o que permite lidar com nomes de arquivos que começam com - corretamente.

* Isto não é necessário aqui, estritamente falando, o único benefício neste cenário é que mantém read de remover qualquer espaço em branco inicial ou final, mas é um bom hábito para entrar em quando você precisa lidar com nomes de arquivos contendo caracteres de nova linha, ou em geral, quando você precisa ser capaz de lidar com nomes de arquivos arbitrários.

    
por 15.09.2017 / 11:16
11

Esse $(cat file_list.txt) em shells POSIX como bash no contexto da lista é o operador split + glob ( zsh só faz a parte split como você esperaria).

Ele se divide em caracteres de $IFS (por padrão, SPC , TAB e NL) e glob, a menos que você desative o globbing.

Aqui, você quer dividir apenas na nova linha e não quer a parte glob, então deve ser:

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

Isso também tem a vantagem (em um loop while read ) de descartar linhas vazias, preservar uma linha final não finalizada e preservar o stdin de mv (necessário no caso de prompts, por exemplo).

No entanto, a desvantagem é que o conteúdo completo do arquivo deve ser armazenado na memória (várias vezes com shells como bash e zsh ).

Com alguns shells ( ksh , zsh e, em menor grau, bash ), você pode otimizá-lo com $(<file_list.txt) em vez de $(cat file_list.txt) .

Para fazer o equivalente com um loop while read , você precisa:

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

Ou com bash :

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

Ou com zsh :

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

Ou com o GNU mv e zsh :

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

Ou com o GNU mv e o GNU xargs e o ksh / zsh / bash:

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

Mais informações sobre o que significa deixar as expansões sem aspas em Implicações de segurança de esquecer de citar uma variável em shells bash / POSIX

    
por 15.09.2017 / 11:23
1

Em vez de escrever um script, você pode usar o find ..

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

para o caso quando os arquivos estão localizados no arquivo, então você pode fazer o seguinte:

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

o segundo não tem certeza ..

Goodluck

    
por 15.09.2017 / 11:19
0
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

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

para FILE em "$ {FILE [0]}"

         omxplayer "$ {FILE [0]}" feito

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

! / bin / bash

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

para FILE em "$ {FILE [0]}"

         play "$ {FILE [0]}" feito

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

! / bin / bash

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

para DIR em "$ {DIR [0]}"

    cd "$ {DIR [0]}" & & encontrar . -type f -name ' .ogg' -o -name ' .mp3' | sort --version-sort | enquanto ler DIR; Faz     tocar "$ DIR"     feito feito

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

! / bin / bash

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

para DIR em "$ {DIR [0]}"

    cd "$ {DIR [0]}" & & encontrar . -type f -name ' .ogg' -o -name ' .mp3' | enquanto ler DIR; Faz     tocar "$ DIR"     feito feito

    
por 06.04.2018 / 01:08

Tags