6

If you have a series of subfolders (like from a to z) and want to run a command on each one of them (like rm *.pdf or ls *.pdf), how do you do that? The "manual" approach would be cd a, rm *.pdf, or ls *.pdf, cd .., cd b, ... That seems too complicated, so I believe there must be an easier approach.

1
  • Are there more subfolders than those you shall work on? So e.g. folders a to z shall be considered but subfolders _ and + shall be ignored? Feb 28, 2013 at 20:19

8 Answers 8

7

Try doing this (using , brace expansion & globs):

rm -f {a..z}/*.pdf

or

rm -f [a-z]/*.pdf

if your shell lack the brace expansion feature.

Contrary to [a-z], {a..z} (also supported by ksh93) is not a glob, it's brace expansion, it's expanded (before globs) regardless of whether files exist or not. That's like rm -f a/*.pdf b/*.pdf..., regardless of whether a, b... exist or not. Also note that contrary to [a-z] where the range may be locale dependant (like may include é, ś...), {a..z} only works with byte ranges (and reliably only in the ASCII letter ranges, and number ranges)

(Merci Stephane Chazelas for explanations)

1
  • Contrary to [a-z], {a..z} (also supported by ksh93) is not a glob, it's brace expansion, it's expanded (before globs) regardless of whether files exist or not. That's like rm -f a/*.pdf b/*.pdf..., regardless of whether a, b... exist or not. Also note that contrary to [a-z] where the range may be locale dependant (like may include é, ś...), {a..z} only works with byte ranges (and reliably only in the ASCII letter ranges, and number ranges) Mar 1, 2013 at 7:06
2

For that particular example:

rm [a-z]/*.pdf

In any shell.

In the general case, with Bourne-like shells, you could put the list of directories in $1, $2... with:

set a b c foo bar...

and then loop over it like:

for i do (cd -- "$i" && cmd -- *.pdf); done

Though of course you can do both in just one command:

for i in a b c foo bar...; do (cd -- "$i" && cmd -- *.pdf); done

The point is to use a subshell (...) so that cd only affects that subcontext.

1

find may be the best solution but it is easily possible in shell (bash):

GLOBIGNORE=.
for subdir in *; do
  test -d "$subdir" || continue
  cd "$subdir"
  ls *pdf
  cd -
done

or even easier

ls */*.pdf
1

If it is just one level of directories, you can simply do

rm */*.pdf

That will delete all the pdf files in all the directories, but nothing else.

In a case like this you may want to review the list of files selected before you do the delete. To do that try

ls */*.pdf

And if the list is long enough to scroll off the screen, pipe it through a pager, eg

ls */*.pdf | more
1

Use find. Something like the following would find all folders (no more than two folders deep), and then execute rm <pathname>/*.pdf from the folder:

find pathwithsubfolders -type d -maxdepth 1 -mindepth 1 -exec rm {}/\*.pdf \;

In my opinion the best thing to do with commands like these (you know, the ones with potential terrible consequences) is to prepend an 'echo' statement in front of the command you are trying to execute ... just to make sure it looks like what you want before you go pulling the trigger.

5
  • Not rather -maxdepth 1 -mindepth 1? Feb 28, 2013 at 20:10
  • You quote the ";" but not the "*"? Feb 28, 2013 at 20:11
  • By default in bash if a glob does not match anything it is not expanded and you get a '*' this encourages the bad habit of not quoting glob characters. The failglob option changes this behaviour.
    – Craig
    Feb 28, 2013 at 20:26
  • That won't work because globs are expanded by shells, not by find or rm. Feb 28, 2013 at 20:32
  • Hey all thanks, you're all right. I have to escape *s and mindepth does it right. Updated my answer.
    – dsummersl
    Feb 28, 2013 at 22:00
0

Well, assuming you want to remove all PDF files from the current directory and all other subdirectories beneath it, you would do something like

find -name "*.pdf" -exec rm -i {} \;

Two things to note:

1- I have added the -i switch to rm so that it prompts you before removing any file in case the find is too broad in scope.

2- I highly encourage you to read the find man page. It is easily one of my all-time favorites.

0

I usually prefer xargs:

find <path> -name '*.pdf' | xargs {ls|rm|etc}

This has the benefit of adding grep or other manipulations in the middle of the pipeline, but has the disadvantage of not gracefully dealing with files that have spaces in the file or path name.

2
  • space, but also tab, newline, single quote, double quote and backslash characters. Feb 28, 2013 at 20:33
  • In other words, uncommon characters, unless you're sharing a FS with windows. Feb 28, 2013 at 21:58
0

Using a portable solution (for Unix-likes)

perl -e '`rm -rf "$_"/*.pdf` for "a" .. "z"'

This will removes the subdirectories recursively under a..z dirs

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.