2

Linux users! I'm having a issue: I have all pdf files in directoryDownloads, but I want it to copy all the files in subfolder \Download\BOOKS\. How would I copy all files with for loop?

for files in ...

  • 1
    Where do you want to copy them to? Also, \Download\BOOKS\ is... wrong. Is Download/BOOKS a subdirectory under your home directory? – RonJohn Jul 12 '18 at 01:20
  • Do you want to move to the subdirectory or promote from it ? What about conflicts ? – mckenzm Jul 12 '18 at 03:28

6 Answers6

9

not with a for loop but this should work to move the files.

mv /home/username/Downloads/*.pdf /home/username/Downloads/BOOKS

Use this to make a copy of the files:

cp /home/username/Downloads/*.pdf /home/username/Downloads/BOOKS
Kevin
  • 155
7

The existing answers are all decent, but have varying minor issues, which may or may not be problematic. Let's try to handle them.

  1. Is *.pdf correct for all the files? I find PDFs downloaded from the internet often are *.PDF instead. (I suspect certain case-insensitive filesystems cause this.)
  2. Are all the PDFs in the BOOKS subdirectory directly, or are there subdirectories? BOOKS/Romance, BOOKS/Horror for example.
  3. Do the PDFs have file extensions at all? Sometimes they don't.
  4. Are any of the files symbolically linked? Unlikely, but not hard to handle.
  5. Already pointed out but bears repeating: Do any of the filenames contain spaces or other odd characters? Spaces are fairly common for human-readable-ness.

The strategy is to use find to locate potential PDFs, file to filter only actual PDFs, and then act on those filenames.

find -L books -maxdepth 1 -print0 | xargs -0 -n1 -I{} sh -c '[ "$(file -b --mime-type "{}")" = "application/pdf" ] && cp "{}" Download/'

find -L Download/BOOKS -maxdepth 1 -print0 will locate all the filesystem entries in the subdirectory tree starting at Download/BOOKS, following symbolic links if needed, to a maximum depth. Increase the depth number to handle subdirectories, or remove -maxdepth 1 to handle the whole tree.

xargs -0 -n1 -I{} takes multiple lines of input and runs a command repeatedly with 1 input line.

sh -c '[ "$(file -b --mime-type "{}")" = "application/pdf" ] && cp "{}" Download/' makes a new shell to run a test on the filesystem entry, and if it is a PDF (as determined by file extension and file contents) make a copy of it to the Download/ directory.

Using find takes care of 1., 2. and 4. Using file takes care of 3. Using the -print0 and -0 arguments and careful quoting takes care of 5.

If you really needed a loop because you want to run other commands on each PDF, replace cp "{}" Download/ with whatever is approriate. The {} is a pattern that xargs will replace with each filename as it runs.

studog
  • 191
5

I don't know about for loops (unless you write a C program to achieve this) but this line should do it:

 cp -v ~/Downloads/*.pdf ~/Downloads/BOOKS/

The CP is copy and the * is for wildcard to select anything ending in .pdf in that directory.

  • 3
    I was going to list all the languages that have for loops (Bash, sh, python) but it's a lot easier to list the languages that don't here it is: – Sam Jul 11 '18 at 20:19
  • 2
    @Sam There are many languages without for loops. Scheme and Haskell, for example. – jbch Jul 11 '18 at 20:39
  • If this is a homework question it probably should be bash. – mckenzm Jul 12 '18 at 03:30
5

I don't think that you require for loop just for copying files. You can copy files using:

cp /home/username/Downloads/*.pdf /home/username/Downloads/BOOKS

But if you still want to use for loop, you can also write the below code:

cd /home/username/Downloads
for F in *.pdf
do
   cp "$F" /home/username/Downloads/BOOKS
done
Kulfy
  • 17,696
3

A simple for loop should be able to do what you need, i.e. copy all PDF files under Downloads (including subdirectories) in to Downloads/BOOKS

cd Downloads
IFS=$(echo -en "\n\b") && for n in `find . -path ./BOOKS -prune -o -name "*.pdf" -print`; do cp -fv "$n" ./BOOKS; done

Note that the single quote for the find statement is the backward single quote, not the normal one.

Edited: Internal field separator needed for spaces in filenames.

Bernard Wei
  • 2,125
  • 1
    Props for respecting the OPs request for a FOR loop solution, but I can't help but want to simplify that to: cd /foo/bar; find ./ -name '*.pdf' -exec cp -fv '{}' ./BOOKS/ \; – immortal squish Jul 11 '18 at 22:59
  • 2
    This one will be subject to word splitting – steeldriver Jul 11 '18 at 23:43
  • 2
    @immortalsquish's answer is not only simpler; it is also correct: the code in this answer does the wrong thing when the path to any PDF under Downloads contains a space. – wchargin Jul 12 '18 at 01:01
  • 1
    Good catch guys. I have updated my answer to correct for this. Good to explicitly define IFS as not everybody have this customized in their bashrc. – Bernard Wei Jul 12 '18 at 18:06
1

A simple for loop will work, but is much more complicated than it needs to be.

for FILE in ~/Downloads/BOOKS/*.pdf; do
  cp "$FILE" /new/directory/
done

This will copy all files in the BOOKS directory to /new/directory

Ogre55
  • 504
  • Doesn't work if any of the files have whitespace in the filenames. – doneal24 Jul 11 '18 at 19:00
  • 2
    Change cp $FILE to cp "$FILE" to prevent word-splitting, as @Doug mentions. – wchargin Jul 11 '18 at 19:21
  • 1
    Also, change /* to /*.pdf to only copy .pdf files. – Kevin Fegan Jul 11 '18 at 19:36
  • @wchargin Still would not work. $FILE will be defined by the split words in the for statement. Quoting it later is closing the barn doors after the horse has gone. – doneal24 Jul 11 '18 at 19:57
  • 2
    @DougO'Neal: That's not true: https://gist.github.com/wchargin/ad87571235ad0bbb02a998d4a2e2c6e3 – wchargin Jul 11 '18 at 20:21
  • The most beautiful solution is a bash script which code is: – andrej benedičič Jul 12 '18 at 17:47
  • `#!/bin/bash

    cd /home/andrej/Prejemi for F in *.pdf do cp "$F" /home/andrej/Prejemi/KNJIGE done `

    – andrej benedičič Jul 12 '18 at 18:10
  • @andrej, why do you say that that is the "most beautiful" solution? To me, a simple cd ~/Prejemi; cp *.pdf KNJIGE/ seems clearly much better. It is simpler, using strictly fewer constructs. It is easier to read and more idiomatic. And it only invokes cp once, so it will be faster with lots of files. – wchargin Jul 13 '18 at 02:47
  • (I'm aware that it doesn't suffice in the case where you get an "argument list too long" error, but it doesn't sound like you have that many PDFs—and if you do, then find -exec or find -print0 | xargs -0 is a better solution than a for loop.) – wchargin Jul 13 '18 at 02:48