Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort the results of find (including nested directories) alphabetically in bash

Tags:

find

bash

sorting

I have a list of directories based on the results of running the "find" command in bash. As an example, the result of find are the files:

test/a/file
test/b/file
test/file
test/z/file

I want to sort the output so it appears as:

test/file
test/a/file
test/b/file
test/z/file

Is there any way to sort the results within the find command, or by piping the results into sort?

like image 645
Ken Avatar asked Jan 23 '13 23:01

Ken


2 Answers

If you have the GNU version of find, try this:

find test -type f -printf '%h\0%d\0%p\n' | sort -t '\0' -n | awk -F '\0' '{print $3}'

To use these file names in a loop, do

find test -type f -printf '%h\0%d\0%p\n' | sort -t '\0' -n | awk -F '\0' '{print $3}' | while read file; do
    # use $file
done

The find command prints three things for each file: (1) its directory, (2) its depth in the directory tree, and (3) its full name. By including the depth in the output we can use sort -n to sort test/file above test/a/file. Finally we use awk to strip out the first two columns since they were only used for sorting.

Using \0 as a separator between the three fields allows us to handle file names with spaces and tabs in them (but not newlines, unfortunately).

$ find test -type f
test/b/file
test/a/file
test/file
test/z/file
$ find test -type f -printf '%h\0%d\0%p\n' | sort -t '\0' -n | awk -F'\0' '{print $3}'
test/file
test/a/file
test/b/file
test/z/file

If you are unable to modify the find command, then try this convoluted replacement:

find test -type f | while read file; do
    printf '%s\0%s\0%s\n' "${file%/*}" "$(tr -dc / <<< "$file")" "$file"
done | sort -t '\0' | awk -F'\0' '{print $3}'

It does the same thing, with ${file%/*} being used to get a file's directory name and the tr command being used to count the number of slashes, which is equivalent to a file's "depth".

(I sure hope there's an easier answer out there. What you're asking doesn't seem that hard, but I am blanking on a simple solution.)

like image 144
John Kugelman Avatar answered Oct 09 '22 14:10

John Kugelman


find test -type f -printf '%h\0%p\n' | sort | awk -F'\0' '{print $2}'

The result of find is, for example,

test/a'\0'test/a/file
test'\0'test/file
test/z'\0'test/z/file
test/b'\0'test/b/text file.txt
test/b'\0'test/b/file

where '\0' stands for null character.

These compound strings can be properly sorted with a simple sort:

test'\0'test/file
test/a'\0'test/a/file
test/b'\0'test/b/file
test/b'\0'test/b/text file.txt
test/z'\0'test/z/file

And the final result is

test/file
test/a/file
test/b/file
test/b/text file.txt
test/z/file

(Based on the John Kugelman's answer, with "depth" element removed which is absolutely redundant.)

like image 43
Peter Evselyev Avatar answered Oct 09 '22 16:10

Peter Evselyev