Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I can use ls -ld */ to list all the directory entries in the current directory. Is there a similarly easy way to just list all the regular files in the current directory? I know I can use find

find . -maxdepth 1 -type f

or stat

stat -c "%F %n" * | grep "regular file" | cut -d' ' -f 3-

but these do not strike me as being overly elegant. Is there a nice short way to list only the regular files (I don't care about devices, pipes, etc.) but not the sub-directories of the current directory? Listing symbolic links as well would be a plus, but is not a necessity.

share|improve this question
What do you mean by "overly elegant"? As far as I know, the find command is the best way to do what you want. For some reliable other options, you should look into shell specific commands (and those are anything but portable)! –  rahmu Sep 18 '12 at 9:58
@rahmu I was for something similar to ls -d */, which is short, easy to type, and easy to understand. So I'm pretty happy with Ulrich Dangel's answer, even though I'm not using zsh. –  daniel kullmann Sep 18 '12 at 10:41

8 Answers 8

up vote 8 down vote accepted

With zsh and Glob Qualifiers you can easily express it directly, e.g:

echo *(.)

will either only return the list of regular files or an error depending on your configuration.

For the non-directories:

echo *(^/)

(will include symlinks (including to directories), named pipes, devices, sockets, doors...)

echo *(-.)

for regular files and symlinks to regular files.

echo *(-^/)

for non-directories and no symlinks to directories either.

Also, see the D globbing qualifier if you want to include Dot files (hidden files), like *(D-.).

share|improve this answer
zsh surely has some real nice features. Maybe I should switch... –  daniel kullmann Sep 18 '12 at 9:05
Obligatory zsh one-short-liner: … Ah, good, it's here already. –  Gilles Sep 18 '12 at 23:43
ls -p | grep -v / 

would work perfectly well

share|improve this answer
+1, this is a good answer. Down voting a new user with no comment is pretty shocking to me, especially when a working answer is provided. The only edge case I can see where this won't work properly is if there is a newline in a filename. Also retaining colours would be a nice addition where supported. For GNU you could add --color=always to the ls, for OSX -G. –  Graeme Feb 23 '14 at 10:38

ls has no option to do that, but one of the nice things about unix & linux is that long-winded and inelegant pipelines can easily be turned into a shell script, function, or alias. and these can, in turn, be used in pipelines just like any other program.

(NOTE: there are some scope issues with functions and aliases. Scripts are available to any executable that can read and execute them. Aliases and functions are only available in the current shell - although a sub-shell's .profile/.bashrc etc may redefine them and thus make them available. Also, a script can be written in any language - including bash/sh, awk, perl, python, and others - whichever one is best for the job or that you are most familiar with)


alias lsf='find . -maxdepth 1 -type f -print0 | xargs -0r ls'

I've added xargs so that you can use use all the usual ls options, e.g. lsf -lrS

Because it uses find, all of the normally-hidden dotfiles will be displayed, and all of the filenames will be prefixed with ./ - that's about the only difference you'll notice.

You could exclude dot files with ! -iname '.*' but then you'd have to have two versions of the alias - one that displayed dot files and one that didn't.

alias lsf2='find . -maxdepth 1 -type f -a ! -iname '\''.*'\'' -print0 | xargs -0r ls'

Alternatively, if lsf was a script rather than an alias you could parse the options (perhaps with getopts or /usr/bin/getopt or similar), and exclude dotfiles unless -a was present.

share|improve this answer
-name is enough, you don't need -iname. –  Totor Jan 2 '14 at 1:13

The manual stated the 'f' option is against 'regular file' only, not pipes, socket or block/char devices, which means you're already doing the right stuff.

   -type c
          File is of type c:

          b      block (buffered) special

          c      character (unbuffered) special

          d      directory

          p      named pipe (FIFO)

          f      regular file

          l      symbolic link; this is never true if the -L option or the
                 -follow option is in effect, unless the symbolic link  is
                 broken.  If you want to search for symbolic links when -L
                 is in effect, use -xtype.

          s      socket

          D      door (Solaris)
share|improve this answer
-bash-4.2$ ls -F | grep -v '[/@=|]' | more

The -F option to ls appends * to executables, / to directories, @ to symbolic links, = to sockets, and | to FIFOs. You can then use grep to exclude the non-regular file characters from output and you have the files. This will work in any shell, not just zsh.

The weaknesses are:

  1. Any files that have those characters will be excluded (but you shouldn't really have files with those characters in the name anyway)
  2. This only works for the case you specified where you are within the directory
  3. You will have an asterisk appended on any file that is executable. That could be handled by piping through sed to remove any '*' characters from the output.
share|improve this answer

This is not particularly short but you could turn it into a script or alias if you needed to call it quickly. Using the ls command as an input array, call ls -ld on each entry piped to grep to exclude directories with the output sent to null, and if successful echo the original input:

for list in `ls` ; do ls -ld $list | grep -v ^d > /dev/null && echo $list ; done ;

You can invert the grep and conditional output, same results:

for list in `ls` ; do ls -ld $list | grep ^d > /dev/null || echo $list ; done ;
share|improve this answer
\dev\null?!?! –  manatwork Oct 7 '13 at 9:07
Good catch, fixed it, thanks. –  Don R Oct 7 '13 at 9:26

Shell builtins only:

printf '2>- cd "%s" || echo "$_"\n' "$PWD/"* |sh

Specifically for regular files, though it might be slower because I think it will stat them:

printf 'test -f "%s" && echo "$_"\n' "$PWD/"* |sh

EDIT: daniel kullman notes that his shell didn't parse the command as expected when using either of the above two forms, but that replacing the $_ special shell parameter with inline parameterized variable assigment was successful. Either form should require nothing more than any basic Posix-compatible shell is specified to provide.

printf 'unset f;test -f "${f=%s}" && echo "$f"' "$PWD/"* |sh

Notice that this is slighty different than in the comments because it's a good idea t make sure that no environment variables interfere when batch processing groups of files. Alternatively the following options will work just as well and be equally as portable:

( printf 'test -f "${f=%s}" && echo "$f"' "$PWD/"* |env -i $0 )
( set -- *; for f; do { [ -f "$f" ] && echo "$f" ; } ; done )


I tested this a little. Avoiding the pipe entirely is definitely faster. This meets all of your expectations, I think, though I can only leave its "elegance" to your interpretation.

( set -- *;for f; do [ ! -d "$f" ] && echo "$f"; done )

If you wanted it as a shell function the conversion is simple:

notd() { while [ $# -gt 0 ]; do { ( 
    2>/dev/null cd "$1" || break
    set -- "$PWD/${1#${1%.}}"*
    for f; do [ ! -d "$f" ] && echo "$f"
    done ) ; shift ; }
done ; }

I nested a loop there - it kind of uses both of the methods in the above oneliners. Here's how it works:

  • It accepts one or more directories as arguments

  • For each of its arguments it opens a subshell in which it attempts to change into the directory.

  • If successful, it sets the subshells's positional parameters to all of the files in the current directory.

  • For each of the new positional parameters it runs a test to check if it is a directory.

  • If not, it prints the positional parameter to stdout, but if so it is ignored.

Anyway, you use it like this:

notd /some/dir /some/other/dir

Any of its arguments that is not a directory into which it can change will be ignored. Any file contained within any of its arguments that is a directory will be ignored. Pretty simple.

One last hint: if you want "hidden" files in your output simply add a "/." to the directory name.

notd ~/.
share|improve this answer
Which shell is that for? Bash doesn't print anything, I guess because $_ is empty –  daniel kullmann Mar 4 '14 at 8:35
It was probably because I forgot the \n newline. Does it work with that added? Oh, and the answer is: all of them that I know of. The only iffy (according to Posix) is printf as it's not required as builtin, though it is required for all implementations, I think. –  mikeserv Mar 4 '14 at 8:39
No. In a directory with two subdirs (1 and 2) and two files (3 and 4), it prints "/bin/sh" twice. Again, what is the meaning of $_? –  daniel kullmann Mar 4 '14 at 8:45
What? You're copying and pasting it exactly? $_ is the Posix specified special shell parameter that refers to the last parameter of the last command. If needed you could get the same out of this: printf 'test -f "${f=%s}" && echo "$f" ;unset f\n' "$PWD/"* |sh –  mikeserv Mar 4 '14 at 8:49
Anyway, I just checked both commands in GNU sh (read: bash), dash and zsh. They both worked as advertised in all three shells. Maybe you have weird shopt parameters set that might affect Posix compatibility. Try running them with sh -c 'COMMAND' to see if your output is any different. –  mikeserv Mar 4 '14 at 8:56

You can try:

ls -al | grep ^-

Where the first character of the list describes the type of file, so - means that it's a regular file.

share|improve this answer
This answer needs more love –  Jonah Mar 29 at 18:52

protected by slm Feb 23 '14 at 9:44

Thank you for your interest in this question. Because it has attracted low-quality answers, posting an answer now requires 10 reputation on this site.

Would you like to answer one of these unanswered questions instead?

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