-1

There are obviously many ways to do the same job in Linux, but I recently needed to for i in xxx through a list of each item, recursively in a tree. I found a solution with tree by doing tree -fail (or tree -faild just to get the directory names), but it made me wonder: would it be possible to get a listing like this by using just ls?

$ tree -fail
.
./.custom
./custom_loader.sh
./.git
./.git/branches
./.git/COMMIT_EDITMSG
./.git/config
./.git/description
./.git/HEAD
./.git/hooks
./.git/hooks/applypatch-msg.sample
YorSubs
  • 621
  • 1
    Do you mean like shopt -s globstar; for i in xxx/**; do echo "$i"; done? Please provide an actual example – Chris Davies Apr 27 '21 at 16:27
  • The actual example is tree -fail, that's the output I mean (i.e. showing one line per file with the full relative path to that file listed). shopt -s globstar; for i in xxx/**; do echo "$i"; done is in the right direction, but ignores all .* files (which are important for my requirements to process every file). – YorSubs Apr 27 '21 at 16:31
  • 1
    If you provide an example in your question we can write answers that address the particular issue(s) you're seeking to solve. You definitely should not be looking to parse the output of ls. – Chris Davies Apr 27 '21 at 16:33
  • Sure, I can update the question with the output of tree -fail. It's just that, but I'll do it. I'm curious, why is parsing the output of ls bad, I'm curious why that might be a problem? As you can see in the above, I get one line per item, whether it is a directory or a file, with the full relative path. It also shows all .* files/directories. – YorSubs Apr 27 '21 at 16:37
  • I don't have tree installed so I can't quickly and easily review its output. You've an answer from Kusalananda so it's largely moot here, but for next time please consider including sample input – Chris Davies Apr 27 '21 at 17:05

1 Answers1

1

The bash shell can recurse into directories with its special ** globbing pattern. The ** pattern matches like *, but also reaches across / in pathnames:

shopt -s globstar nullglob dotglob

for dirpath in ./**/; do printf '%s\n' "$dirpath" # whatever other code needs to be run on "$dirpath" done

I'm enabling globstar to get access to **, and I'm also enabling nullglob and dotglob to be able to skip the loop entirely if the pattern does not match anything , and to also see hidden files.

The pattern ./**/ would match any directory in or below the current directory, just like ./*/ would match any directory in the current directory.

Use ./**/* or ./** to list all types of files.

You can also do this portably with find:

find . -type d -exec sh -c '
    for dirpath do
        printf "%s\n" "$dirpath"
        # whatever other code needs to be run on "$dirpath"
    done' sh {} +

Here, find feeds the loop in the sh -c script with pathnames of directories.

The only difference that you will see when running these two examples is that the first example (the bash loop with **) will resolve symbolic links to directories, while find will not resolve symbolic link to directories.

If all you want is to list directories, then the find example may be shortened significantly into

find . -type d -print

Use -type f in place of -type d to see only regular files, and delete the -type test completely to see all types of files.

You could obviously also use ls to get such a list, but I don't really see much point in it as you can't do anything but look at the output from ls anyway.

shopt -s globstar dotglob

ls -1d ./**

Note that this command runs the risk of triggering an "argument list too long" error if the list of pathnames that the pattern expands to is too long. None of the other commands in this answer has that issue.

Also note that it isn't really ls that does any recursing here, it's the shell expanding the ./** pattern that recurses, and it does that to create the list of arguments for ls before ls is even invoked.

Kusalananda
  • 333,661