Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Result of a normal find using find . ! -path "./build*" -name "*.txt":


and when sorted with sort -n:


however the desired output is:


which means output is sorted based on filename only, but folder information should be maintained as part of the output.

Edit: Make example more complicated as the subdirectory structure may include more than one level.

share|improve this question
See this question I asked on SO: stackoverflow.com/questions/3222810/… – camh May 23 '11 at 2:23
@camh - if possible I would like to use only unix commands. In any case my question is pretty much a duplicate of yours. Can you transfer the best solution to this thread (keep a link to the original anyways) so I can mark is as the solution? – Unode May 23 '11 at 8:51
If @Shawn makes the changes I suggested in my comment (use -printf instead of awk), I think that is the best solution. I've reworked my original implementation to use this method. – camh May 23 '11 at 10:43
up vote 9 down vote accepted

You need to sort by the last field (considering / as a field separator). Unfortunately, I can't think of a tool that can do this when the number of fields varies (if only sort -k could take negative values).

To get around this, you'll have to do a decorate-sort-undecorate. That is, take the filename and put it at the beginning followed by a field separator, then do a sort, then remove the first column and field separator.

find . ! -path "./build*" -name "*.txt" |\
    awk -vFS=/ -vOFS=/ '{ print $NF,$0 }' |\
    sort -n -t / |\
    cut -f2- -d/

That awk command says the field separator FS is set to /; this affects the way it reads fields. The output field separator OFS is also set to /; this affects the way it prints records. The next statement says print the last column (NF is the number of fields in the record, so it also happens to be the index of the last field) as well as the whole record ($0 is the whole record); it will print them with the OFS between them. Then the list is sorted, treating / as the field separator - since we have the filename first in the record, it will sort by that. Then the cut prints only fields 2 through the end, again treating / as the field separator.

share|improve this answer
Since this is with find(1), you can skip the awk part and use -printf '%f/%p\n' – camh May 23 '11 at 3:20
indeed our setup is slightly more complicated. It does include variable subdir depths. Edited the question to reflect this fact. My apologies for not including this at first. – Unode May 23 '11 at 8:44
@Unode: Shawn's solution handles variable depth just fine, it's the canonical solution to this problem (up to minor variations). – Gilles May 23 '11 at 9:40

In zsh ≥4.3.10:

print -l -- **/*.txt~build*(oe\''REPLY=${REPLY:t}'\')
  • **/*.txt matches *.txt in the current directory and its subdirectories recursively.
  • ~build* excludes matches whose text begins with build* (like ! -path './build*'). (You need setopt extended_glob first.)
  • (oe\''…'\') is a sorting glob qualifier. REPLY=… constructs the string to sort from the string to return.
  • ${REPLY:t} is the basename (“tail”) of the path.
share|improve this answer
Lots of concatenated magic. Interesting but we are limited to sh syntax. +1 – Unode May 23 '11 at 8:37

I would use files '-printf' to output name and path, sort by name, and cutoff the name in a last step. '###' is just a marker, to help cutting.

find -name "*.txt" -printf "%f###%p\n" | sort -n | sed 's/.*###//'

%f prints the filename, %p the whole path.

I simplified the find-command to get it into one line, of course you would leave the ! -path "./build*" part.

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.