I have a directory in which I would like to list all the content (files and sub directories) without showing the symbolic links. I am using GNU utilities on Linux. The ls version is 8.13.


Full directory listing:

~/test$ ls -Gg
total 12
drwxrwxr-x 2 4096 Jul  9 10:29 dir1
drwxrwxr-x 2 4096 Jul  9 10:29 dir2
drwxrwxr-x 2 4096 Jul  9 10:29 dir3
-rw-rw-r-- 1    0 Jul  9 10:29 file1
-rw-rw-r-- 1    0 Jul  9 10:29 file2
lrwxrwxrwx 1    5 Jul  9 10:29 link1 -> link1
lrwxrwxrwx 1    5 Jul  9 10:30 link2 -> link2

What I would like to get

~/test$ ls -somthing (or bash hack)
total 12
dir1 dir2 dir3 file1 file2

NOTE: My main motivation is to do a recursive grep (GNU grep 2.10) without following symlinks.


For the stated question you can use find:

find . -mindepth 1 ! -type l

will list all files and directories in the current directory or any subdirectories that are not symlinks.

mindepth 1 is just to skip the . current-directory entry. The meat of it is the combination of -type l, which means "is a symbolic link", and !, which means negate the following test. In combination they match every file that is not a symlink. This lists all files and directories recursively, but no symlinks.

If you just want regular files (and not directories):

find . -type f

To include only the direct children of this directory, and not all others recursively:

find . -mindepth 1 -maxdepth 1

You can combine those (and other) tests together to get the list of files you want.

To execute a particular grep on every file matching the tests you're using, use -exec:

find . -type f -exec grep -H 'some pattern' '{}' +

The '{}' will be replaced with the files. The + is necessary to tell find your command is done. The option -H forces grep to display a file name even if it happens to run with a single matching file.

  • It doesn't show the files in witch the pattern is found. – TheMeaningfulEngineer Jul 9 '14 at 8:56
  • That's what you run grep for (see last paragraph). – Michael Homer Jul 9 '14 at 8:57
  • I doesn't work for me. It just outputs the the found patterns without a filename where it was found. – TheMeaningfulEngineer Jul 9 '14 at 9:09
  • 2
    Use grep -H, if you have it (see man grep), or otherwise grep 'pat' '{}' /dev/null ';' to trick it into thinking there are multiple files if you don't. – Michael Homer Jul 9 '14 at 9:13
  • 1
    + fails to meet that requirement in the degenerate case where there is only one file or one file left at the end. – Michael Homer Jul 9 '14 at 20:10

Or, simpler:

ls -l | grep -v ^l


ls -l means to list in long form. When you do this, the first string of characters gives information about each file. The first character indicates what type each file is. If it's a symlink, then an l is the first character.

grep -v ^l means to filter out (-v) the lines that start with (^) an l.

  • This works great, but how to get only folder names? I wanna use this in for i in – Luka Oct 31 '17 at 14:28
  • Can you explain this code please? – abg Dec 21 '17 at 14:37
  • Beware that this solution does not work if you ommit -l in ls. So this would not work as expected: ls | grep -v ^l – stamster Apr 11 '18 at 14:06
  • @Luka You don't loop over the output of find, see unix.stackexchange.com/questions/321697 – Kusalananda Jun 22 '18 at 16:05
  • 1
    @abg - I added an explanation. Hope it helps. – Geoff Aug 8 '18 at 17:34

From version 2.12 onwards, the -r option for GNU grep doesn’t dereference symbolic links unless you specify them by hand:

-r, --recursive

Read all files under each directory, recursively, following symbolic links only if they are on the command line. This is equivalent to the -d recurse option.

-R, --dereference-recursive

Read all files under each directory, recursively. Follow all symbolic links, unlike -r.

  • What version of grep is that. I have -R, -r, --recursive Read all files under each directory, recursively; this is equivalent to the -d recurse option. in 2.10 – TheMeaningfulEngineer Jul 9 '14 at 8:45
  • grep (GNU grep) 2.14 – tijagi Jul 9 '14 at 10:16

In zsh, this would be easy thanks to glob qualifiers:

grep -- PATTERN **/*(.)

The pattern **/ traverses subdirectories recursively. The glob qualifier . restricts matching to regular files.

Without zsh, use find (see Michael Horner's answer). And in this particular case, GNU grep can do what you want (it's exactly what grep -r does) — but only since version 2.12, earlier versions did follow symbolic links.


You can use $LS_COLORS to do this. If your version of ls supports specifying the colors using that variable, you can define output per file type. It's builtin behavior and very configurable. So I created some files to demo this like:

for f in 9 8 7 6 5 4 3 2 1
    do touch "${f}file" && 
    ln -s ./"${f}file" ./"${f}filelink"

So now I'll do:

LS_COLORS='lc=:rc=:ec=:ln=\n\n\0HERE_THERE_BE_A_LINK>>\0:' \
ls -1 --color=always | cat





And the nulls are there too...

LS_COLORS='lc=:rc=:ec=:ln=\n\n\0HERE_THERE_BE_A_LINK>>\0:' \
ls -1 --color=always | sed -n l

You can specify for all or any filetypes. Doing so for only a single filetype might not get you want though as ls has incorporated some defaulted compilation values for terminal escapes. You'd do much better to address the api as a single interface. Here's a simple little means of parsing and assigning current environment dircolors configured defaults:

set -- di fi ln mh pi so do bd cd or su sg ca tw ow st ex
for fc do printf %s "$fc=/$fc//:"
done) ls -l --color=always | cat

Its output in my home directory looks like this:

total 884
///-rw-r--r-- 1 mikeserv mikeserv    793 Jul  9 11:23 /fi//1/
//drwxr-xr-x 1 mikeserv mikeserv    574 Jun 24 16:50 /di//Desktop//
//-rw-r--r-- 1 mikeserv mikeserv    166 Jul  4 23:02 /fi//Terminology.log/
//-rw-r--r-- 1 mikeserv mikeserv      0 Jul  6 11:24 /fi//new
//lrwxrwxrwx 1 mikeserv mikeserv     10 Jul 11 04:18 /ln//new
link/ -> /fi//./new
//-rwxr-xr-x 1 mikeserv mikeserv    190 Jun 22 11:26 /ex//script.sh/*
//-rw-r--r-- 1 mikeserv mikeserv 433568 Jun 22 17:10 /fi//shot-2014-06-22_17-10-16.jpg/
//-rw-r--r-- 1 mikeserv mikeserv     68 Jun 17 19:59 /fi//target.txt/

You can run that with cat -A too and the only difference you'll encounter is that you'll see $ for newlines - there are no unprintable characters introduced by ls --color=always with this configuration - only what you see here.

ls inserts its default terminal escapes like this:


...where the default values for $lc (left of code), $rc (right of code), and $rs (reset) are:

 \033 - ESCAPE
 0 - reset 

...respectively. ${type_code} is used to stand in for the various fi (regular file - default unset), di (directory), ln (link), and every other file type I know of. There is also $no (normal) which is also by default unset and which is here represented by the // at the beginning of each line. My simple little IFS=: block works just by inserting the name for each configurable in also as its own value and adding a slash or two - though \0NUL bytes would do as well.

By default ls will also insert one $rs immediately preceding its first output $lc - but this is not accurately represented here. In this case I have specified $ec (end code) which stands in for $rs in all cases - when it is specified you don't get an extra $rs between $no and ${type_code} as you would otherwise - it only presents immediately following a filename and once at the start of output - as you can see in the one extra slash at the head of line one.

Here's a snippet from my own $LS_COLORS

printf %s "$LS_COLORS"


And, in truth, my little shell hack is probably overly complicated - there's a widely available interface for assigning these values. Try dircolors -p in your cli and info dircolors for more information on that.

You can wrap the filenames in arbitrary strings. You can comment them out if you wish. You can specify similar behaviors based only on file extension. There's really not a lot you can't specify that way.

Now I'm not just making all of this up either - I learned about it after stumbling on the source code by accident.

With this particular configuration ls will emit:

  1. $no - once per record at the start of each record

  2. ${type_code} - once immediately preceding each filename to include an abbreviation of the file's type and always occurring on the same line as and 7 whitespace delimited fields after $no or immediately following a -> denoting a symbolic link's target.

  3. $ec - once immediately preceding the very first line and thereafter only once immediately following each filename.

  4. All other values are empty.

What follows is a null-delimited ls, and this time I will use cat -A, though, without it, it would look the same as the last example :

set -- di fi ln mh pi so do bd cd or su sg ca tw ow st ex
for fc do printf %s "$fc=/$fc//\0:"
done) ls -l --color=always | cat -A

total 884$
^@//^@//-rw-r--r-- 1 mikeserv mikeserv    793 Jul  9 11:23 /fi//^@1^@//$
^@//drwxr-xr-x 1 mikeserv mikeserv    574 Jun 24 16:50 /di//^@Desktop^@///$
^@//-rw-r--r-- 1 mikeserv mikeserv    166 Jul  4 23:02 /fi//^@Terminology.log^@//$
^@//-rw-r--r-- 1 mikeserv mikeserv      0 Jul  6 11:24 /fi//^@new$
^@//lrwxrwxrwx 1 mikeserv mikeserv     10 Jul 11 04:18 /ln//^@new$
link^@// -> /fi//^@./new$
^@//-rwxr-xr-x 1 mikeserv mikeserv    190 Jun 22 11:26 /ex//^@script.sh^@//*$
^@//-rw-r--r-- 1 mikeserv mikeserv 433568 Jun 22 17:10 /fi//^@shot-2014-06-22_17-10-16.jpg^@//$
^@//-rw-r--r-- 1 mikeserv mikeserv     68 Jun 17 19:59 /fi//^@target.txt^@//$

And so to reliably remove all symbolic links from a -long listing like this one, you might make a simple change:

LS_COLORS='rs=:no=//:lc=:rc=:ec=/ :'$(
set -- di fi mh pi so do bd cd or su sg ca tw ow st ex
for fc do printf %s "$fc=$fc/:"
done)ln=///: ls -l --color=always | sed ':ln

My results after running that look like...

total 884
-rw-r--r-- 1 mikeserv mikeserv    793 Jul  9 11:23 fi/1/ 
drwxr-xr-x 1 mikeserv mikeserv    574 Jun 24 16:50 di/Desktop/ /
-rw-r--r-- 1 mikeserv mikeserv    166 Jul  4 23:02 fi/Terminology.log/ 
-rw-r--r-- 1 mikeserv mikeserv      0 Jul  6 11:24 fi/new
-rwxr-xr-x 1 mikeserv mikeserv    190 Jun 22 11:26 ex/script.sh/ *
-rw-r--r-- 1 mikeserv mikeserv 433568 Jun 22 17:10 fi/shot-2014-06-22_17-10-16.jpg/ 
-rw-r--r-- 1 mikeserv mikeserv     68 Jun 17 19:59 fi/target.txt/ 

Using some command like to the one I do above:

LSCOLORS=...$(...)fc1=///:fc2=///: ls ... | sed ...

...(where fc1 and fc2 are filetypes listed after set -- in the subshell) should serve to reliably remove any combinations of filetypes you could want from ls output regardless of any characters the filenames might contain.

  • Heh, nice, +1 for originality. Seems to choke on directories though, they're printed with a garbled ANSI color code. Anyway, could you add an explanation of how this works? Perhaps explain what lc, nc, etc are? – terdon Jul 11 '14 at 9:40
  • @terdon - They're only printed with the ANSI escapes already set in your environment and or compiled in as a default for ls's di= value. You'd need to add di=: to the var to nullify that - and for all others. It's what I mean by the api - you address each filetype but you have to address the interface the same in all cases. So setting ln one way and ignoring di defaults won't work out. There are many defaults compiled in as well... Hmm. Your asking about lc and nc and stuff is valid - completely - I already did it the other day though. I'll link it, I think. – mikeserv Jul 11 '14 at 9:57
  • Ouch, so I'd have to either specify all possible file types always or check what's present before hand? OK, thanks. – terdon Jul 11 '14 at 10:00
  • @terdon - see my edit just now - it does it all in a couple lines of shell, specifying each type by its type name and reliably delimiting every file name. There are no nonprintables introduced either. And there's an api for that anyway - it's dircolors. It reads a config file and outputs a shell eval friendly var value. It's pretty easy - but so are the few lines up there. Anyway, if you wanted to use dircolors you just save two different files and invoke it that way. Also, I showed you the other day how to do both at once with \0NUL delimiters as part of each escape sequence. – mikeserv Jul 11 '14 at 10:25
  • @terdon - I tried to make it clearer how easy it is to set all of the values that might be compiled in - I think I got em all, maybe too many... LS_COLORS='rs=:no=\0//:lc=:rc=:ec=\0//:'$( set -- di fi ln mh pi so do bd cd or su sg ca tw ow st ex; for fc do printf %s "$fc=/$fc//\0:"; done) ls -l --color=always | cat -A – mikeserv Jul 11 '14 at 12:20

The answer above lead to the following:

 ls -1F | grep -i "/"

Oddly enough leaving the 1 out of the ls command still gives a list.

  • 1
    Why grep -i? It's not as if / can be upper or lower-case. ls defaults to a noe-column format when the output is not a terminal (it's a pipe here). – Kusalananda Jun 22 '18 at 16:08

Try this one:

ls | grep -v " -> "
  • 2
    That will fail for various reasons, for example on a file called a -> b. It will also fail on file names with newlines and other strangeness. Please read this for why parsing ls is a bad idea. – terdon Jul 9 '14 at 11:30
  • @terdon There are more fundamental reasons why this will fail. Either ls is aliased to ls -l, and this returns columns before the file name, or it isn't, and this doesn't do anything special about symbolic links. – Gilles Jul 10 '14 at 21:01
  • @terdon - ls parses itself. we all parse ls every time we print to a terminal - there's an api. I posted an answer demonstrating something like it here - but you can do a lot more. I showed you just the other day - you had null-delimited filenames yourself. – mikeserv Jul 10 '14 at 21:51
  • @mikeserv I know, I know. However, explaining how to do this safely takes several hundred lines as you yourself have demonstrated. As it stands, this answer suffers from all the dangers of parsing ls and the OP is clearly not aware of them. – terdon Jul 11 '14 at 9:30
  • @terdon - ls isn't going to eat your children or anything - it's only a program. If you don't explain it then it will take several hundred to explain it and several hundred more to explain why the several hundred lines you just linked to are wrong. And besides - this is only two lines: LS_COLORS='lc=:rc=:ec=:ln=\n\n\0HERE_THERE_BE_A_LINK>>\0:' \ ls -1 --color=always | cat – mikeserv Jul 11 '14 at 11:24

Try this command: ls -p | grep -v @

  • Nope, lists the links. – TheMeaningfulEngineer Jul 9 '14 at 8:47
  • @Alan: k try this find -type f – Thushi Jul 9 '14 at 8:58
  • 1
    -1 because: i) The -p just adds a slash to directories, you're looking for -F ii) your find alternative will not list directories and iii) Please test your answers before posting. – terdon Jul 9 '14 at 11:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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