How to enter every directory in current path and execute script

17,006

Solution 1

If you can, use find as suggested in other answers, xargs is almost always to be avoided.

But if you still want to use xargs, a possible alternative is the following:

printf '%s\0' */ | xargs -0 -L1 bash -c 'cd -- "$1" && pwd' _

Some notes:

  1. */ expands to the list of directories in the current folder, thanks to the trailing slash

  2. printf with \0 (null byte) separates the elements one for each line

  3. the option -L1 to xargs makes it to execute once for every input line and the option -0 makes it separate the input on the null byte: filenames can contain any character, the command doesn't break!

  4. bash removes the double quotes and pass it to the inline script as a single parameter, but cd should put double quotes again to interpret it as a single string; using -- makes the cd command robust against filenames that start with a hyphen

  5. to avoid the strange use of $0 as a parameter, is usual to put a first dummy argument _

Solution 2

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

Swap pwd for your script. And . for the root directory name, if it's not the "current" directory.

You have to wrap the exec clause in bash -c "..." because of the way -exec works. cd doesn't exist in its limited environment, but as you can see by running the above command, when you bring in bash to do the job, everything runs as predicted.

Edit: I'm not sure why 2011 Oli didn't think of -execdir but that's probably a faster solution:

find . -type d -execdir pwd \;

Solution 3

You shouldn't parse ls in the first place.

You can use GNU find for that and its -execdir parameter, e.g.:

find . -type d -execdir realpath "{}" ';'

or more practical example:

find . -name .git -type d -execdir git pull -v ';'

and here is version with xargs:

find . -type d -print0 | xargs -0 -I% sh -c 'cd "%" && pwd && echo Do stuff'

For more examples, see: How to go to each directory and execute a command? at SO

Solution 4

Other alternative:

for directory in *
do
  (cd $directory && awk '{print $1" && pwd"}')
done

Solution 5

Just for curiosity I tried to take your example and make it work. There is no reason to use this technique instead of the find command.

Note: Thanks to enzotib for pointing it out, this does not work for directory names containing spaces or any other characters that make awk think the name consists of several fields.

To make your example work a few tweaks should be made. But at first see the code:

ls -l | grep ^d | awk '{print $NF}' | xargs -n 1 bash -c 'cd $0; ls'

The first two commands make sure that only directories are listed. Since ls long listing contains a d as the first character for directories, the grep command selects only these lines.

With awk only the last column is displayed in every line. NF is a built-in variable in awk holding the maximum number of fields. Thus printing $NF displays the last field.

Finally xargs needed to be tamed to run the specified script with only one parameters at a time. (I know this kind-of defeats its purpose.) Otherwise the script could not be run for every directory without a for loop.

And to work around the built-in cd barrier, we call bash with -c. Also note that $0 becomes the first parameter instead of $1.

Share:
17,006
UAdapter
Author by

UAdapter

Updated on September 18, 2022

Comments

  • UAdapter
    UAdapter over 1 year

    I want to enter every directory retuned by ls command and execute script.

    I tried this (and many other things), but it just does not work

    ls  | awk '{print $1" && pwd"}' | xargs cd
    

    How to do it without for loop?

    • geirha
      geirha over 12 years
      Why specifically ls and not a for-loop? The for-loop would be the easier approach, and in addition it won't break on directories containing whitespace or quotes, like you'll get problems with when parsing ls output.
    • kenorb
      kenorb over 9 years
  • enzotib
    enzotib over 12 years
    The reason cd alone do not work is because it is a shell builtin, nothing to do with the environment.
  • enzotib
    enzotib over 12 years
    No need to quote {}, and a -maxdepth 1 could be useful, if the OP do not want to recurse into subdirectories.
  • enzotib
    enzotib over 12 years
    Unfortunately, if you have directories names containing spaces, $NF gives only the last word of the name. This is one of the reason why people is suggested to not parse ls output.
  • lgarzo
    lgarzo over 12 years
    This is a valid point and I do not see an easy workaround without creating a lengthy awk script. I was trying to demonstrate how the original thinking could work (and to convince the OP not to use it, because it is at least not elegant). Thank you for pointing it out, I'll update the answer.
  • UAdapter
    UAdapter over 12 years
    @enzotib I did not know about the -maxdepth 1, thx
  • enzotib
    enzotib over 12 years
    See my answer working with xargs.
  • lgarzo
    lgarzo over 12 years
    I knew echo * but was not aware of */. And putting an underscore as the 1st argument is a neat trick.
  • UAdapter
    UAdapter over 12 years
    thx a lot, I'm learning bash and this is very helpful.
  • UAdapter
    UAdapter over 12 years
    thx a lot, I'm learning bash and this is very helpful.
  • Todd Partridge 'Gen2ly'
    Todd Partridge 'Gen2ly' almost 12 years
    Nice work Oli. I added maxdepth here as mentioned before, also since it seems to imply current subdirectories i removed .: find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && pwd" \;
  • enzotib
    enzotib over 8 years
    @gniourf_gniourf: thank for editing and providing a more robust command.
  • bwduncan
    bwduncan over 5 years
    This is actually better than find, as it can use xargs in parallel (-P) much more easily.