14

I can show the target file that a link points to using ls -l:

snowch$ ls -l /usr/local/bin/mvn
lrwxr-xr-x  1 snowch  admin  29 12 Dec 08:58 /usr/local/bin/mvn -> ../Cellar/maven/3.2.3/bin/mvn

Is there a way to show less output without having to pipe through another command such as awk? E.g:

snowch$ ls ?? /usr/local/bin/mvn
/usr/local/bin/mvn -> ../Cellar/maven/3.2.3/bin/mvn

I'm running 3.2.53 on OS X 10.9.5. The output from several commands is shown below:

snowch$ ls -H /usr/local/bin/mvn
/usr/local/bin/mvn

snowch$ ls -L /usr/local/bin/mvn
/usr/local/bin/mvn

snowch$ file /usr/local/bin/mvn
/usr/local/bin/mvn: POSIX shell script text executable

snowch$ file -b /usr/local/bin/mvn
POSIX shell script text executable
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
Chris Snow
  • 4,046
  • 4
  • 23
  • 30

4 Answers4

8

ls unfortunately doesn't have an option to retrieve file attributes and display them in an arbitrary way. Some systems have separate commands for that (for instance GNU has a stat command or the functionality in GNU find).

On most modern systems, with most files, this should work though:

$ ln -s '/foo/bar -> baz' the-file
$ LC_ALL=C ls -ldn the-file | sed '
   1s/^\([^[:blank:]]\{1,\}[[:blank:]]\{1,\}\)\{8\}//'
the-file -> /foo/bar -> baz

That works by removing the first 8 blank delimited fields of the first line of the output of ls -l. That should work except on systems where the gid is not displayed there or the first 2 fields are joined together when there's a large number of links.

With GNU stat:

$ LC_ALL=C stat -c '%N' the-file
'the-file' -> '/foo/bar -> baz'

With GNU find:

$ find the-file -prune \( -type l -printf '%p -> %l\n' -o -printf '%p\n' \)
the-file -> /foo/bar -> baz

With FreeBSD/OS/X stat:

f=the-file
if [ -L "$f" ]; then
  stat -f "%N -> %Y" -- "$f"
else
  printf '%s\n' "$f"
fi

With zsh stat:

zmodload zsh/stat
f=the-file
zstat -LH s -- "$f"
printf '%s\n' ${s[link]:-$f}

Many systems also have a readlink command to specifically get the target of a link:

f=the-file
if [ -L "$f" ]; then
  printf '%s -> ' "$f"
  readlink -- "$f"
else
  printf '%s\n' "$f"
fi
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
  • `stat -f "%N -> %Y" -- /usr/local/bin/mvn` worked great. Thanks! – Chris Snow Dec 12 '14 at 10:51
  • 1
    On an old RHEL6 system, stat is of GNU coreutils 8.4 and there it's a `stat -c %N -- /usr/local/bin/mvn`. To remove the quotations I had to pipe this into `| perl -pe 's/['"'"'\`]//g'` – cfi Aug 31 '16 at 09:00
  • @cfi `tr -d \'\\`` would be enough. Note that that RHEL system would have GNU `find` with which you'll have more control over. – Stéphane Chazelas Aug 31 '16 at 09:26
6

Use the file command.

[sreeraj@server ~]$ ls -l mytest
lrwxrwxrwx 1 sreeraj sreeraj 15 Dec 12 09:31 mytest -> /usr/sbin/httpd

[sreeraj@server ~]$ file mytest
mytest: symbolic link to `/usr/sbin/httpd'

or

[sreeraj@server ~]$ file -b mytest
symbolic link to `/usr/sbin/httpd'
[sreeraj@server ~]$

Also, please go read through man page of ls and check the options -L and -H and see if that would suffice your requirement.

Sreeraj
  • 4,984
  • 10
  • 38
  • 57
  • @Sree - Many thanks! I had tried the `ls` options, but not `file`. Unfortunately, neither seems to work :( – Chris Snow Dec 12 '14 at 10:04
  • @SHC Sorry, I assumed that you are on a `linux` box. Please add `osx` as one of your tags. That should get to the attention of `osx` users as well. I have edited the post to add the tag, but I don't have enough credits for the edit to appear immediately. – Sreeraj Dec 12 '14 at 10:08
  • maybe check 'readlink' utility on osx. – tonioc Dec 12 '14 at 10:35
2

With a GNU ls at least (and, apparently, tcsh's implementation) you can hack the $LS_COLORS environment variable to insert delimiters where you like (but tcsh's builtin ls-F doesn't do link targets - only link flags) Usually ls inserts arbitrary non-printable terminal escapes based on the values stored within that environment var, but there's nothing stopping us from inserting arbitrary anything else instead. More on this here.

For example:

LS_COLORS='ln=///\n:lc=:no=//:rc=:rs=:' \
\ls ~ -l --color=always | 
sed '\|///|,\|//|!d;//d'

That puts a string like // at the head of every listing (so just before lrwcrwx) and a ///\n just before the filename of any link. sed then filters on line ranges - it will delete every input line until it encounters /// and from there through the next line which matches // it will delete lines matching //. So it only gets the link name and link target - regardless of intervening characters. This is because / can't occur in a filename - and those in any path ls might print will only occur singly.

See?

mkdir test; cd test
touch 'long


name' shortname
ln -s l* "$(printf %s.ln l*)"; ln -s s* shortname.ln

LS_COLORS='ln=///\n:lc=:no=//:rc=:rs=:' \
\ls  -l --color=always | sed '\|///|,\|//|!d;//d'

...which prints:

long


name.ln -> long


name
shortname.ln -> shortname

Try it yourself.

mikeserv
  • 57,448
  • 9
  • 113
  • 229
  • Do you know of any implementation besides modern versions of GNU `ls` that supports `LS_COLORS` that way and a `--color=always` and options after arguments? If on a GNU system, why would you use something that convoluted when you can use `find` or `stat`? That also probably won't work if you do some `ln -s /// some-file`. – Stéphane Chazelas Dec 12 '14 at 11:48
  • @StephaneChezales - [this](http://www.bigsoft.co.uk/blog/index.php/2008/04/11/configuring-ls_colors) indicates at least that it should work similarly with a bsd `ls`. I remember reading that somewhere else months ago about `ls`s all tending to accept a termcap like syntax. Good point about the `ln -s ///` thing - but `\0`NULs work as well - and `-R`ecursively. As to why - well, it's easy to use. In some cases easier than `find` - and it seemed a little more on topic here than `find` would have been. In any case, not too many people consider it, so I mention it when I'm reminded. – mikeserv Dec 12 '14 at 11:57
  • See [the man page](http://www.freebsd.org/cgi/man.cgi?query=ls) or [there](http://unix.stackexchange.com/a/2904) for FreeBSD. If you want to test things on FreeBSD, you can use live CDs like GhostBSD/mfsbsd in a VM. – Stéphane Chazelas Dec 12 '14 at 12:09
  • @StéphaneChazelas - no, i dont need to test it - definitely a [wholly different animal](https://github.com/freebsd/freebsd/blob/master/bin/ls/print.c#L474). If you care to look, the gnu source is also linked to in the link in this answer. i wonder where i read that other thing? thanks very much for pointing that out though, that was a mistaken assumption im happy to discard. ive edited the answer. – mikeserv Dec 12 '14 at 12:29
  • recent versions of `tcsh` have a `ls-F` builtin that supports `LS_COLORS` the same way as some versions of GNU `ls`, but it resorts to invoking `ls -F` if you pass any option to it, so that won't work here unless `ls` is GNU `ls` on your system. – Stéphane Chazelas Dec 12 '14 at 13:09
  • @StéphaneChazelas - what do you mean by that exactly - sorry i dont follow this one...? i only wrote it in because in the same google search with which i found bsd's source i also found tcsh's env docs. theyre pretty eplicit there about how it should be used - moreso than the gnu folks - most of what the [tcsh docs say](http://www.tcsh.org/tcsh.html/ENVIRONMENT.html) i gathered from the gnu source. it seems weird they would be so explicit in a shell doc about a possibly unrelated command. – mikeserv Dec 12 '14 at 13:16
  • @StéphaneChazelas - ok. so you mean `--color-always` and [`$color`](http://www.tcsh.org/tcsh.html/Special_shell_variables.html) probably. it might still work, though, one would have to be pretty devoted to doing it. a tty in between would be necessary i guess - maybe just a `exec 3<>/dev/tty` beforehand - *(if `tcsh`will do it, classic `csh`s probably wouldnt)*. as i said before, i only wrote it up at all because it seemed relevant - and it can be handy in different ways for noting different file-types/extensions with different delims *(or different numbers of such)* especially for extensns – mikeserv Dec 12 '14 at 13:33
  • You need `-l` to retrieve the symlink target, so `ls-F` won't do. – Stéphane Chazelas Dec 12 '14 at 13:55
  • @StéphaneChazelas - oh. duh. `ls-F` makes that obvious enough, I guess. guess it just didn't occur to me what they'd do an `ls` that didn't do `ls` stuff. – mikeserv Dec 12 '14 at 14:05
-1

[On Linux / Bash]

I'd do this:

ls -l | sed -e"s/ \+/ /g" | cut -d' ' -f 9-

The sed command collapses multiple spaces to a single space; the cut extracts the 9th field onwards where the field-separator is a space.

Typical output from this:

libboost_atomic.a
libboost_atomic.so -> libboost_atomic.so.1.57.0
libboost_atomic.so.1.57.0
...
libboost_wserialization.a
libboost_wserialization.so -> libboost_wserialization.so.1.57.0
libboost_wserialization.so.1.57.0

Alternatives are:

ls -l | awk '{ print $9, $10, $11 }'
ls -l | awk '{ $1 = $2 = $3 = $4 = $5 = $6 = $7 = $8 = "" ; print }'

But those are imperfect: the first can output trailing spaces; the second, leading spaces.

Rhubbarb
  • 99
  • 2
  • 1
    The question explicitly stated "... without having to pipe through another command ..." – Jeff Schaller Jul 14 '15 at 16:25
  • @JeffSchaller: Good point; I agree. I hope, though, that this is still of use to someone wanting an answer to the title question (without the detail in the description). – Rhubbarb Jul 14 '15 at 16:31