Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
    
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? –  Hauke Laging Feb 28 '13 at 20:19
add comment

8 Answers

up vote 6 down vote accepted

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)

share|improve this answer
    
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) –  Stephane Chazelas Mar 1 '13 at 7:06
add comment

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.

share|improve this answer
add comment

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.

share|improve this answer
    
Not rather -maxdepth 1 -mindepth 1? –  Hauke Laging Feb 28 '13 at 20:10
    
You quote the ";" but not the "*"? –  Hauke Laging Feb 28 '13 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 '13 at 20:26
    
That won't work because globs are expanded by shells, not by find or rm. –  Stephane Chazelas Feb 28 '13 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 '13 at 22:00
add comment

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.

share|improve this answer
add comment

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
share|improve this answer
add comment

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
share|improve this answer
add comment

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.

share|improve this answer
    
space, but also tab, newline, single quote, double quote and backslash characters. –  Stephane Chazelas Feb 28 '13 at 20:33
    
In other words, uncommon characters, unless you're sharing a FS with windows. –  PaulProgrammer Feb 28 '13 at 21:58
add comment

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

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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