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.
-
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 LagingFeb 28, 2013 at 20:19
8 Answers
Try doing this (using bash, 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)
-
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 likerm -f a/*.pdf b/*.pdf...
, regardless of whethera
,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
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.
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
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
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.
-
-
-
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.– CraigFeb 28, 2013 at 20:26 -
That won't work because globs are expanded by shells, not by
find
orrm
. 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. Feb 28, 2013 at 22:00
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.
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.
-
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
Using a perl portable solution (for Unix-likes)
perl -e '`rm -rf "$_"/*.pdf` for "a" .. "z"'
This will removes the subdirectories recursively under a..z
dirs