6

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?

2
  • 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.
    – geirha
    Nov 9, 2011 at 15:57
  • See also: How to go to each directory and execute a command? at stackoverflow.
    – kenorb
    Oct 21, 2014 at 20:15

7 Answers 7

18
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 \;
2
  • The reason cd alone do not work is because it is a shell builtin, nothing to do with the environment.
    – enzotib
    Nov 9, 2011 at 15:07
  • 4
    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" \; May 29, 2012 at 16:59
9

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 _

4
  • I knew echo * but was not aware of */. And putting an underscore as the 1st argument is a neat trick.
    – lgarzo
    Nov 9, 2011 at 15:59
  • thx a lot, I'm learning bash and this is very helpful.
    – UAdapter
    Nov 10, 2011 at 9:40
  • 1
    @gniourf_gniourf: thank for editing and providing a more robust command.
    – enzotib
    Oct 5, 2015 at 17:24
  • This is actually better than find, as it can use xargs in parallel (-P) much more easily.
    – bwduncan
    Oct 19, 2018 at 16:37
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

2

The find command

The find command is pretty handy. It can generate a list of files or directorise in or below a specified directory, and it can also execute a command on each item in that list of results. (Note: if you're using a multi-user system, make sure to read the security considerations section of the Findutils documentation for the find command.)

The options to the find command that we need are -type d which tells find to return the names of every directory within the specified path, and -exec command '{}' \; which tells find to run command on the results. For example:

find /media/music/flac -type d -exec ~/tag-flac-with-rg.sh '{}' \;

This tells find to return every directory in or below the path /media/music/flac and then run the tag-flac-with-rg.sh script on each directory found. The '{}' is replaced by each directory name found, and gets passed as an argument to the tag-flac-with-rg.sh script. The \; marks the end of the arguments list to the tag-flac-with-rg.sh script. may be this will help u

2
  • 1
    No need to quote {}, and a -maxdepth 1 could be useful, if the OP do not want to recurse into subdirectories.
    – enzotib
    Nov 9, 2011 at 15:11
  • @enzotib I did not know about the -maxdepth 1, thx
    – UAdapter
    Nov 9, 2011 at 15:42
2

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.

4
  • 1
    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.
    – enzotib
    Nov 9, 2011 at 15:15
  • 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.
    – lgarzo
    Nov 9, 2011 at 15:23
  • See my answer working with xargs.
    – enzotib
    Nov 9, 2011 at 15:43
  • thx a lot, I'm learning bash and this is very helpful.
    – UAdapter
    Nov 10, 2011 at 9:40
2

Maybe take a look at using find with -exec?

2

Other alternative:

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .