9

Let's say my main directory is /home/test

Under it I have lots of subdirectories and under subdirectories still a lots of them and so on.

Example:

/home/test
/home/test/subdir1
/home/test/subdir1/subdir2
/home/test/subdir1/subdir2/subdir3
/home/test/subdir1/subdir2/subdir4
/home/test/subdir1/subdir2/subdir4/subdir5

and so on ...

I want a simple script that takes each directory and just runs the pwd command.

  • 3
    What is the point of this exercise?  Is it simply to produce a list of the names of all the directories?  Or, as your title suggests, do you have some other command that you want to invoke in each directory, and you're just using pwd as a proof of concept? – G-Man Says 'Reinstate Monica' May 17 '15 at 03:16
  • youweek1, below, you commented "pwd was just an example... i need to run more commands in each subdirectory" - note pwd is pretty special in many ways - that's what causes confusion as it is used as example command. – Volker Siegel May 17 '15 at 11:31

5 Answers5

8

Solution Using Parallel

You could use GNU Parallel for a compact, faster solution.

find . -type d -print0 | parallel -0 cd {}'&&' <command-name>

This will work absolutely fine, even for directory names containing spaces and newlines. What parallel does here is that it takes the output from find, which is every directory and then feeds it to cd using {}. Then if changing the directory is successful, a separate command is run under this directory.

Regular Solution using while loop

find "$PWD" -type d | while read -r line; do cd "$line" && <command-name>; done;

Note that $PWD is used here because that variable contains the absolute path of current directory from where the command is being run. If you don't use absolute path, then cd might throw error in the while loop.

This is an easier solution. It will work most of the time except when directory names have weird characters in them like newlines (see comments).

shivams
  • 4,565
  • Not sure how robust this will work otherwise, but at least the variable expansions need to be quoted. Try with a space in $PWD or some subdirectory name. – Volker Siegel May 17 '15 at 11:13
  • @VolkerSiegel : Thanks. I've improved the solution. Now, it should work fine. – shivams May 17 '15 at 11:55
  • 1
    These fail for directory names that contain newlines. – G-Man Says 'Reinstate Monica' May 17 '15 at 20:54
  • 1
    The second one also fails for directory names ending in blanks. There's also a problem in that the command is run regardless of whether cd succeeds or not. – Stéphane Chazelas May 17 '15 at 21:00
  • @G-Man: Yes, they will fail in that case. Is their a fix for such cases? – shivams May 17 '15 at 21:07
  • 1
    @shivams: You generally want to use -print0 with find, to get the output in an unambiguous form (nul-separated rather than newline-separated).  For the second example, use read -d $'\0' -r line to read the output from find.  For the first one, check whether parallel has an option that tells it to expect the input to be nul-separated rather than newline-separated; it might be called something like -0, -z, or --null.  (Or, for a different approach, see my answer.) – G-Man Says 'Reinstate Monica' May 17 '15 at 21:24
  • 2
    GNU Parallel has -0 and --null. OP writes that he has subdirectories. So it is likely not a malicious user. I have yet to see a dir name with \n that was made by a non-malicious user. Spaces, yes. Quotes, yes. Star, tildes and pipes, yes. But never \n. – Ole Tange May 17 '15 at 22:39
5

The find command is powerful, but that makes it a little challenging to use.
I'm pretty sure it can do what you need - this command below is "almost" what you ask for:

find . -type d -execdir pwd \;

But - this does not run the command in the deepest directory level - it runs in the directories in which other directories are found.

So it wil run in subdir4, because it contains subdir5. But not in subdir5 - as you probably expect.

It's important that the -execdir option is used, not the more well-known -exec (see man find):

The -exec option runs the command in the start directory, and has other drawbacks too:

    -exec command ;
           Execute command; true if 0 status  is  returned.   All  following
           arguments  to find are taken to be arguments to the command until
           an argument consisting of ';' is encountered.  The string '{}' is
           replaced  by  the current file name being processed everywhere it
           occurs in the arguments to the command,  not  just  in  arguments
           where  it  is  alone, as in some versions of find.  Both of these
           constructions might need to be escaped (with a \ ) or quoted  to
           protect  them from expansion by the shell.  See the EXAMPLES sec‐
           tion for examples of the use of the -exec option.  The  specified
           command  is  run once for each matched file.  The command is exe‐
           cuted in the starting directory.   There are unavoidable security
           problems  surrounding use of the -exec action; you should use the
          -execdir option instead.

But the option -execdir is just what you ask for:

   -execdir command ;
   -execdir command {} +
           Like  -exec,  but the specified command is run from the subdirec‐
           tory containing the matched  file,  which  is  not  normally  the
           directory  in  which  you  started find.  This a much more secure
           method for invoking commands, as it avoids race conditions during
           resolution  of the paths to the matched files.  As with the -exec
           action, the '+' form of -execdir will build  a  command  line  to
           process  more  than one matched file, but any given invocation of
           command will only list files that exist in the same subdirectory.
           If  you use this option, you must ensure that your $PATH environ‐
           ment variable does not reference '.'; otherwise, an attacker  can
           run any commands they like by leaving an appropriately-named file
           in a directory in which you will run -execdir.  The same  applies
           to having entries in $PATH which are empty or which are not abso‐
           lute directory names.
Volker Siegel
  • 17,283
3

One of the other answers came close to this:

find . -type d -exec sh -c 'cd "$0" && cmd' {} \;

(running the command only if the cd succeeds).  Some people recommend inserting a dummy argument, so the found directory ({}) slides over to $1:

find . -type d -exec sh -c 'cd "$1" && cmd' foo {} ";"

Of course, you can use any string here in place of foo.  Common choices include -, --, and sh.  This string will be used in error messages (if any), e.g.,

foo: line 0: cd: restricted_directory: Permission denied

so sh seems to be a good choice.  \; and ";" are absolutely equivalent; that’s just a style preference.

The above commands execute the shell once for each directory.  This may come with some performance penalty (especially if the command to be executed is relatively light-weight).  Alternatives include

find . -type d -exec sh -c 'for d; do (cd "$d" && cmd); done' sh {} +

which executes the shell once, but forks once for each directory, and

find . -type d -exec sh -c 'save_d=$PWD; for d; do cd "$d" && cmd; cd "$save_d"; done' sh {} +

which doesn’t spawn any subshells.  If you are searching an absolute path, as your question suggests, you can combine the above and do

find /home/test -type d -exec sh -c 'for d; do cd "$d" && cmd; done' sh {} +

which doesn’t spawn any subshells and also doesn’t have to bother with saving the starting point.

  • Note that POSIX shells already set $OLDPWD, so you don't need to save an extra copy in $save_d. The POSIX syntax to loop over "$@" is for d do not for d; do (though the latter is also widely supported (except in the Bourne shell which is not a POSIX shell anyway)). – Stéphane Chazelas May 17 '15 at 21:07
  • Yes, but if cmd is replaced by a sequence of commands including other cd commands, then $OLDPATH will no longer be the directory that the find is running from. – G-Man Says 'Reinstate Monica' May 17 '15 at 21:10
  • True. Note that except with bash and pdksh based implementations of sh, if cmd is an external command, (cd "$d" && cmd) does not fork an extra process (compared to the same without the subshell) because cmd is executed directly in the subshell process, so it would be a shame to avoid it on performance grounds in those cases. – Stéphane Chazelas May 17 '15 at 21:15
  • Useful to know.  But, again, I believe that you’re assuming that cmd is a single command.  (cd "$d" && cmd₁ && cmd₂) would need to fork an extra process, wouldn’t it? – G-Man Says 'Reinstate Monica' May 17 '15 at 21:31
  • Not if the last cmd is external (and the shell is not bash or pdksh based) – Stéphane Chazelas May 18 '15 at 06:00
0

You don't need to run extra pwd command you can run the following command:

find test/ -type d

You can replace your directory with test/

PersianGulf
  • 10,850
0

You can use something like this

find /home/test/ -type d -exec bash -c '<Command to be executed>' \; 

Example:

The below command will display the dirname, similar to running a pwd command.

find /home/test/ -type d -exec bash -c 'echo $0' \;
cuonglm
  • 153,898
rahul
  • 1,181