16

I have a list of files, with their full paths, one per line in a file "files.txt"

I was trying to move all of those files from their original location to a new directory.

I CDed into the directory where they live now and issued

for file in ~/Desktop/files.txt do mv $file ~/newfolder

but nothing happens. I am sure I am missing something obvious

Oliver Salzburg
  • 86,445
  • 63
  • 260
  • 306
Steve
  • 507
  • 1
  • 6
  • 11
  • 1
    You need some separators there, try to just echo it with: `for file in ~/Desktop/files.txt; do echo $file; done`. – nerdwaller Jan 18 '13 at 17:01
  • 1
    @nerdwaller that won't work, bash will just print "~/Desktop/files.txt" it won't read the file unless explicitly told to either by `for n in $(cat files.txt); do something; done` or `while read n; do something; done < files.txt`. – terdon Jan 18 '13 at 17:24
  • @terdon, I wanted to edit it when I realized I was on non-thinking autopilot but it was past the expiration. Thanks for pointing it out and providing the fix :) – nerdwaller Jan 18 '13 at 17:25

8 Answers8

16

bash won't read the contents of the file unless you tell it to.

for file in $(cat ~/Desktop/files.txt); do mv "$file" ~/newfolder; done
Nifle
  • 34,203
  • 26
  • 108
  • 137
  • 1
    If this is on a single line, you would need `;` between `$()` and `do`. – nerdwaller Jan 18 '13 at 17:20
  • 1
    @nifle Would this work if the files all had different paths, when a path might be `/Users/lombardi/work files/myfile.png` and another in the same file.txt `/docs/file.jpg`? – Steve Jan 18 '13 at 17:56
  • 2
    Useless use of cat, and your script breaks in multiple ways, even if a file just contains a space in its path. Please fix this, as this kind of a common cause for problems. – slhck Jan 18 '13 at 18:06
  • @slhck - I don't agree on the `useless use of cat` dogma, especially for oneliners like this. Finding out what cat does if you are unfamilliar is simple, trying to understand what `< ~/Desktop/files.txt` is not as straight forward. – Nifle Jan 18 '13 at 19:14
  • @slhck - I do however agree that adding `"` around `$file` is just good sense. – Nifle Jan 18 '13 at 19:16
  • Yes, the superfluous `cat` surely doesn't hurt. I just wouldn't encourage it. One could also do `$( – slhck Jan 18 '13 at 19:17
  • 3
    This still breaks on file names that contain spaces. Since `IFS` is set to any white space, a file name like `Quarterly report.pdf` will fail above, _even though_ `$file` is quoted, since Bash will split loop arguments on the whitespace: first `$file` will contain `"Quarterly"` and on the next iteration it will contain `"report.pdf"`. One workaround is to set `IFS='\n'`, but a failsafe solution already exists in the `< files.txt` version. This is one of the reasons that it is not good to learn people to loop like this, since it _will_ break sooner or later. – Daniel Andersson Jan 18 '13 at 20:29
  • First line should say `for file in $(> ~/Desktop/files.txt)`? Spaces are still causing a problem when I try it that way. – Brian Z Aug 21 '16 at 18:50
8

You need to tell your loop to read the file, otherwise it is just executing:

mv ~/Desktop/files.txt ~/newfolder

In addition, as nerdwaller said, you need separators. Try this:

while read file; do mv "$file" ~/newfolder; done < ~/Desktop/files.txt

If your paths or file names contain spaces or other strange characters, you may need to do this:

while IFS= read -r file; do mv "$file" ~/newfolder; done < ~/Desktop/files.txt

Notice the quotes " around the $file variable.

terdon
  • 52,568
  • 14
  • 124
  • 170
  • Thanks for editing mine, I realized my error but was unable to correct (comment edit time limit) and was writing an answer when yours popped up. Thanks! – nerdwaller Jan 18 '13 at 17:21
  • Thanks, that works too but I found the other post to be easier to remember for the future. – Steve Jan 18 '13 at 17:47
  • No worries, they are both correct. – terdon Jan 18 '13 at 17:50
  • I was going to make the same edit as you did, thanks :) (Sorry, it's just that there are *so* many "good enough" or mostly wrong Bash loops around on the Internet…) – slhck Jan 18 '13 at 18:15
  • @slhck Actually, my original command (without the `IFS` and `-r`) work perfectly well with file names containing spaces as long as the variable name is quoted. I am still not sure when it will break and the IFS and -r are necessary. I think they never are for a file with filenames on each line. I included it just in case :). – terdon Jan 18 '13 at 18:17
  • 1
    Yeah, I just checked. With `while`, it works somewhat okay. It's just the `-r` option should be used to retain backslashes, and setting `IFS=` makes `read` not trim any leading or trailing whitespace. It's `for` that causes issues. See: [Don't Read Lines With For](http://mywiki.wooledge.org/DontReadLinesWithFor) – slhck Jan 18 '13 at 18:29
  • can you please explain the while IFS= read -r thing?? IFS= assigns new value to IFS (where do you restore the old? what value do you assign? I don't see anything but the result of the read-r command, and that value is surely not your desired delimiter?) I'd like to see few words of explanation. – Motti Shneor May 01 '17 at 11:45
  • @MottiShneor `IFS=` just clears `IFS`, it sets it to nothing. And I don't restore it because since it is set only in the `while` loop, it is only changed for the subshell of the loop. It will not affect the value of the `IFS` outside the loop. – terdon May 01 '17 at 13:38
5

If the filenames do not contain whitespace:

mv -t dest_dir $(< text.file)

is probably the most concise way.

If there is whitespace in the filenames

while IFS= read -r filename; do mv "$filename" dest_dir; done < test.file

is safe.

glenn jackman
  • 25,463
  • 6
  • 46
  • 69
  • The mv command only supports the -t flag on systems with the GNU version of mv; this version won't work on OS X or non-GNU UNIX derivatives. – Craig Finch Jan 29 '19 at 17:56
3

I usually do it with

cat filename | xargs -I {} mv {} /new/dir/path

but not sure right now it works with spaces in filenames.

Flo
  • 131
  • 1
1

Try this:

python -c "import shutil; [shutil.move(_.strip(), 'new') for _ in open('files.txt').readlines()];"

Miao Xu
  • 11
  • 1
  • (1) Can you explain this?  (And have you tested it?)  (2) Does this have any advantage over the other answers? … … … … … … … … … … … … … … … … … … … Please do not respond in comments; [edit] your answer to make it clearer and more complete. – Scott - Слава Україні Jan 23 '19 at 08:18
  • I am attempting this on a mac. mv -t is not available. That short, one-liner that uses python is perfect, and for someone that uses python regularly, easier to remember than a bash invocation. To use, substitute 'new' with the path to move the files to, and 'files.txt' with the file containing the filenames. It also handles white-space in front of or behind the file paths. I used it after capturing a slew of files that git told me I needed to remove before checking out a branch. – Cognitiaclaeves Feb 28 '19 at 16:01
  • Nope. Reads the entire contents of the file list in as if it's one long filename then complains that that file doesn't exist. Would only work if the file contained one single line and nothing more, which defeats the purpose. – John Smith Dec 15 '20 at 09:32
0

Possibly

mv -t dest_dir $(sed 's|^|"|;s|$|"|' text.file)

also deals with filenames with spaces while still using a single mv command and no loop. Untested.

  • There are three other answers, all upvoted and one accepted. This answer is unexplained code that you labeled as untested. Why would someone use this answer? – fixer1234 Feb 20 '16 at 18:03
0

BASH code (you can script this):

i=0
IFS=$'\n'                         ## split only on newlines
for line in $(cat files_test.txt)    
do
    echo "$line"
    let ++i                       ## increment counter
done
unset IFS                         ## reset IFS to default
printf 'total: %s lines\n' $i

Test (complex file names):

$ pwd; ls -l

  /mnt/Vancouver/domains/buriedtruth.com/docs
  total 5520
  -rw-r--r-- 1 victoria victoria    1537 Feb 14 11:10 files_test.txt

$ cat files_test.txt | wc -l
  12

$ i=0

$ IFS=$'\n'                         ## split only on newlines

$ for line in $(cat files_test.txt)    
  ## snip

file:///mnt/Vancouver/projects/files/andrighetto2010complex.pdf
file:///mnt/Vancouver/projects/files/florini1996evolution.pdf
file:///mnt/Vancouver/FC/Psychology/Lesson Plan -- The Story of the Third Wave.mp4
file:///mnt/Vancouver/Downloads/downloads.archived/UFO/Did the AAWSA program, AATIP really start in 2007%3F.pdf
file:///mnt/Vancouver/Downloads/downloads.archived/UFO/Outbound to the Moon -- with a UFO%3F [James Oberg -- Jul 2015; apollo-11-ufo-3].pdf
file:///mnt/Vancouver/Downloads/downloads.archived/UFO/Buzz Aldrin re Apollo 11  %22I saw this illumination....%22.mkv
file:///mnt/Vancouver/Downloads/downloads.archived/Ötzi %7C Otzi %7C Iceman/Unexplained Files S01E02 Livestock Mutilation & Curse of the Ice Mummy (cattle mutilations; Otzi; 2013).mkv
file:///mnt/Vancouver/Downloads/downloads.archived/UFO/Contact 2019 -- S01/contact.2019.s01e06.anatomy.of.a.cover.up (UFOs and nuclear sites).mkv
file:///mnt/Vancouver/Downloads/downloads.archived/UFO/Robert M. L. Baker -- Exocosmology  Gravitational Waves & Brain -- Location of Exobiological Intelligence -- Exoklima 2.0.mkv
file:///mnt/Vancouver/FC/Consciousness/Rethinking Consciousness -- QA with Michael Graziano [behavioralscientist.org].pdf
file:///mnt/Vancouver/Downloads/downloads.archived/60 Minutes/Nuremberg Prosecuter Ben Ferencz (60 Minutes S49E33, 2017) -- Transcript -- What the last Nuremberg prosecutor alive wants the world to know.pdf
file:///mnt/Vancouver/Downloads/downloads.archived/Economist -- Can Norway Help US Break the Reoffending Cycle?.mkv

$ unset IFS                         ## reset IFS to default
$ printf 'total: %s lines\n' $i
  total: 12 lines

Reference; https://askubuntu.com/questions/344407/how-to-read-complete-line-in-for-loop-with-spaces

Victoria Stuart
  • 473
  • 3
  • 5
0
#!/bin/bash

# Check if the correct number of arguments is provided
if [ $# -ne 2 ]; then
    echo "Usage: $0 <file_list> <destination_directory>"
    exit 1
fi

file_list="$1"
destination_directory="$2"

# Check if the file list exists
if [ ! -f "$file_list" ]; then
    echo "File list not found: $file_list"
    exit 1
fi

# Check if the destination directory exists
if [ ! -d "$destination_directory" ]; then
    echo "Destination directory not found: $destination_directory"
    exit 1
fi

# Read each filename from the file list and move it to the destination directory
while IFS= read -r filename; do
    if [ -f "$filename" ]; then
        echo "Moving $filename to $destination_directory"
        mv "$filename" "$destination_directory"
    else
        echo "File not found: $filename"
    fi
done < "$file_list"

echo "File move completed."

Save the script in a file, for example, move_files.sh. Make the script executable using the command chmod +x move_files.sh. Then you can run the script by providing the file list and the destination directory as arguments:

./move_files.sh <file_list> <destination_directory>

Replace <file_list> with the path to your file containing the list of filenames, and <destination_directory> with the path to the directory where you want to move the files.

Note: The script assumes that the filenames listed in the file are either absolute paths or relative paths to the current directory. If the file names are relative to a different directory, you may need to modify the script accordingly.

svp
  • 1
  • 2