3

I have the following file structure:

raw:
    F1:
        file1.pdf
    F2:
        file2.pdf
    (...)
pdf:
    (...)

There is always exactly one pdf file per folder, and the file/folder names could be arbitrary.

I want to copy all pdf files in the raw directory to the pdf directory and replace their filenames with their root folder names. For the example above this would be the result:

raw:
    F1:
        file1.pdf
    F2:
        file2.pdf
    (...)
pdf:
    F1.pdf
    F2.pdf
    (...)

I have read about wildcard copying, but this is obviously a problem because I would have to use two nested wildcards to copy my files. So this obviously does not work:

cp raw/*/*.pdf pdf/*.pdf

Is there a way around this or do I have to loop over my folders and use regex to split the paths?

  • You will have to use a loop over all files or directories. I assume the file raw/F1/file1.pdf should be copied to pdf/F1.pdf using the original directory name as part of the file name. Is it necessary to check that the numbers in F1 and file1.pdf are the same? Is it guaranteed that there is only one file in every directory F1 F2` etc? Please edit your question to confirm / clarify. Don't use comments to answer. – Bodo Sep 18 at 8:10
  • @Bodo thanks for the pointers, I have updated my question. The numbers are arbitrary and shouldn't matter and yes, there is always only one single pdf file per subfolder. – T A Sep 18 at 8:13
  • Does your expected result mean that you want to move the files and remove the directories instead of copying? – Bodo Sep 18 at 8:55
  • @Bodo No, I omitted this to use up less space. I will add it again, you are right that this might be confusing. – T A Sep 18 at 9:35
3

Use nested loops:

shopt -s nullglob  # To prevent failures if there's no pdf in a dir.
for dir in raw/* ; do
    for pdf in "$dir"/*.pdf ; do
        cp -- "$pdf" pdf/"${dir#raw/}".pdf  # Remove the "raw/" part
    done
done

or iterate over the files and apply the parameter expansion twice to generate the target name:

for pdf in raw/*/*.pdf ; do
    target=${pdf#raw/}  # Remove the "raw/" part
    target=${target%/*}  # Remove the filename
    cp -- "$pdf" pdf/"$target".pdf
done
| improve this answer | |
  • @terdon: The -- aren't needed, "$pdf" always starts with raw/. – choroba Sep 18 at 9:54
  • @choroba oh, duh. Of course, sorry! – terdon Sep 18 at 11:27
4

You can copy with the wildcards, that's fine. However, you need to rename the files, not just copy them which means you have to assign a new name to each file and that means a loop is unavoidable. At best you can use a tool that does the looping for you.

If you have perl-rename (called renamed on Debian-based systems), you can do:

$ rename -n 's|raw/([^/]+)/.*pdf|pdf/$1.pdf|' raw/*/*.pdf
raw/F1/file1.pdf -> pdf/F1.pdf
raw/F2/file2.pdf -> pdf/F2.pdf

That will find all filenames ending in .pdf that are in any of the first level subdirectories of raw, rename them to the name of their parent folder plus a .pdf extension and save them under pdf/. The -n means "just print what you will do, don't do it", so if you are satisfied this works, run the command without the -n to actually rename the files:

rename 's|raw/([^/]+)/.*pdf|pdf/$1.pdf|' raw/*/*.pdf

Alternatively, you can use a shell loop:

for f in raw/*/*pdf; do 
    new=$(sed -E 's|raw/([^/]+)/.*|pdf/\1.pdf|' <<<"$f"); 
    echo "mv -- $f $new"; 
done
mv raw/F1/file1.pdf pdf/F1.pdf
mv raw/F2/file2.pdf pdf/F2.pdf

Here too, if you are satisfied it works, remove the echo to actually rename the files:

for f in raw/*/*pdf; do 
    new=$(sed -E 's|raw/([^/]+)/.*|pdf/\1.pdf|' <<<"$f"); 
    mv -- "$f" "$new"; 
done

IMPORTANT: Both solutions assume you only have one file per directory so there can be no collisions. They will delete any extra files you have since al files in the same subdir will get the same name.

You could try using the -i option of each respective tool (mv an rename) which will cause them to ask you for confirmation before overwriting a file with the same name. That would make the solutions:

rename -i 's|raw/([^/]+)/.*pdf|pdf/$1.pdf|' raw/*/*.pdf

and

for f in raw/*/*pdf; do 
    new=$(sed -E 's|raw/([^/]+)/.*|pdf/\1.pdf|' <<<"$f"); 
    mv -i -- "$f" "$new"; 
done
| improve this answer | |
3

With zsh:

autoload -Uz zmv # best in ~/.zshrc
zmv -C 'raw/(*)/*.pdf' 'pdf/$1.pdf'
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.