6

I'm trying to combine arguments to the ls command to list a directory content. What I'm basically trying to achieve is listing the directory dereferencing all links, but showing explicitly that the item listed is a link. I've tried combining the --dereference and --classify options, but instead of showing the link symbol (@) I get a * since the link targets an executable file.

Any thoughts on how I can get such result? I'm open to alternatives other than ls command.

EDIT: I actually use other options from the ls command. My current command along with their options is the one bellow:

ls -ogq -LB --group-directories-first --time-style=long-iso

I intend to parse the output inside an application I'm building and can't use the default link output with the arrow (->). Other items on the directory (e.g. folders and files) must also be listed.

EDIT (2):

Just to clarify, I'm developing a Java apllication that uses a SSH2 API to connect to servers and list directories. The result from the listing is then used to populate a tree using the jsTree jQuery plugin . Currently The command I cited above give's me the following output:

felipe@simba:/mnt/drive$ ls -ogq --group-directories-first --time-style=long-iso 
total 12
drwxr-sr-x 2 4096 2014-06-11 18:04 folder1
drwxr-sr-x 6 4096 2014-06-27 19:35 folder2
dr-Sr-s-wt 2 4096 2014-06-27 13:51 folderWithPermissions
-rw-r--r-- 1    0 2014-06-30 10:42 file.txt
lrwxrwxrwx 1   49 2014-06-30 11:36 linkTeste -> folder2/dir/otherfile.txt

By applying a regular expression I can identify what is a folder, file or link by looking at the permissions. But when I have a link I need to list only the link name, instead of name -> destination. If I use the -L option for the ls command only the link name is outputed and I get the referenced destination's permissions (what is a good thing), since the -L option dereferences the link, but this way I can't know that the link is actually a link.

felipe@simba:/mnt/drive$ ls -ogq -L --group-directories-first --time-style=long-iso 
total 12
drwxr-sr-x 2 4096 2014-06-11 18:04 folder1
drwxr-sr-x 6 4096 2014-06-27 19:35 folder2
dr-Sr-s-wt 2 4096 2014-06-27 13:51 folderWithPermissions
-rw-r--r-- 1    0 2014-06-30 10:42 file.txt
-rwxr-xr-x 1    0 2014-06-27 18:40 linkTeste

I need to list only the link name, know it is a link and know what the destination is (file, folder or link). I can handle different kinds of output since I'm going to apply a regular expression anyway.

Alternatives using find or stat are also welcome.

  • 1
    That can lead to all sorts of problems. Avoiding ls altogether is probably the way to go and certainly so if this is to be used in a program. What is it you actually need from the ls command? What file types are you interested in? Anything that ls tells you, stat and/or find can tell you better. If you edit your question with the exact details you need to know, I'll update my answer. – terdon Jun 30 '14 at 17:36
  • Updated the question! – Felipe Leão Jun 30 '14 at 20:06
5

Edit in response to updated question

Since you only care about links, directories and regular files, and don't need to deal with the other filetypes that ls can identify (FIFOs, sockets etc), you could do something like stat. For the examples below, I have created the following test environment:

$ ls -l
total 4.0K
-rw-r--r-- 1 terdon terdon    0 Jun 30 23:12 a new?line 
-rw-r--r-- 1 terdon terdon    0 Jun 30 23:12 a space
-rw-r--r-- 1 terdon terdon    0 Jun 30 23:12 a?tab
drwxr-xr-x 2 terdon terdon 4.0K Jun 30 23:11 dir1
lrwxrwxrwx 1 terdon terdon    4 Jun 30 23:13 linktodir1 -> dir1
lrwxrwxrwx 1 terdon terdon    7 Jun 30 23:13 sh -> /bin/sh

As you can see, these include links, links to executables, a file name with a space, one with a tab (\t) and one with a newline (\n). Most of these files would break your ls approach, but stat can deal with them correctly:

$ stat --printf "%A\t%N\t%F\n" * 
-rw-r--r--  ‘a new\nline’   regular file
-rw-r--r--  ‘a space’   regular file
-rw-r--r--  ‘a\ttab’    regular file
drwxr-xr-x  ‘dir1’  directory
lrwxrwxrwx  ‘linktodir1’ -> ‘dir1’  symbolic link
lrwxrwxrwx  ‘sh’ -> ‘/bin/sh’   symbolic link

The relevant sections of man stat:

--printf=FORMAT

like --format, but interpret backslash escapes, and do not output a mandatory trailing newline. If you want a newline, include \n in FORMAT

%A     access rights in human readable form

%F     file type

%N     quoted file name with dereference if symbolic link

Note that fields are separated by \t, this means you will be able to deal with whitespace within fields (in file names for example) gracefully.

You mentioned that you can't deal with ->. I'm not entirely sure why, but you could either just remove that with sed

$ stat --printf "%A\t%N\t%F\n" * | sed 's/->//' 
lrwxrwxrwx  ‘linktodir1’  ‘dir1’    symbolic link

or substitute it with another string:

$ stat --printf "%A\t%N\t%F\n" * | sed 's/->/→/' | grep linktodir
lrwxrwxrwx  ‘linktodir1’ → ‘dir1’   symbolic link

or just parse the file type.


Depending on what you want to do, it might be useful to separate each of the three file types you are searching for and deal with each separately. If so, use find1 and its -printf option:

$ find ./ -maxdepth 1 -mindepth 1 -type f -printf '%M\t%P\t%l\n'  ## files
$ find ./ -maxdepth 1 -mindepth 1 -type d -printf '%M\t%P\t%l\n'  ## directories
$ find ./ -maxdepth 1 -mindepth 1 -type l -printf '%M\t%P\t%l\n'  ## links

In this case, the printf directives are

          %M     File's permissions (in symbolic form, as for  ls).   This
                 directive is supported in findutils 4.2.5 and later.
          %P     File's  name  with  the name of the command line argument
                 under which it was found removed.
          %l     Object of symbolic link (empty string if file  is  not  a
                 symbolic link).

You could also combine the above into a single command (using find's -o operator) but which lets you use -printf to print an arbitrary string depending on the file type. For example:

$ find ./ -maxdepth 1 -mindepth 1 \( -type l -printf 'link:\t%M\t%P\t%l\n' \) \
-o \( -type d -printf 'dir:\t%M\t%P\n' \) \
-o \( -type f -printf 'file:\t%M\t%P\n' \) 
file:   -rw-r--r--  a?tab
file:   -rw-r--r--  a space
link:   lrwxrwxrwx  linktodir1  dir1
file:   -rw-r--r--  a new?line
dir:    drwxr-xr-x  dir1
link:   lrwxrwxrwx  sh  /bin/sh

The command above will interpret \t and \n correctly if its output is not shown on a terminal. However, to deal with file names with newlines correctly you will need to be careful when parsing (make sure a "line" begins with [file|dir|link]:) or use \0 as a line terminator in each printf call instead of \n:

$ find ./ -maxdepth 1 -mindepth 1 \( -type l -printf 'link:\t%M\t%P\t%l\0' \) \
-o \( -type d -printf 'dir:\t%M\t%P\0' \) \
-o \( -type f -printf 'file:\t%M\t%P\0' \)

1 -maxdepth and -mindepth are GNU extensions, so this approach will only work for GNU find.


The following were posted as solution to the first, less specific version of the question. I am leaving them here since they may be useful to others.

  1. Shell and readlink

    for f in *; do 
     readlink "$f" >/dev/null && echo "$(readlink -f "$f") (link)" || echo "$f";
    done
    

    Example output:

    /etc (link)
    foo
    sample.R
    sample.R~
    

    The above iterates through all files and directories under the current one and if readlink returns successful (if $f is a link), it will dereference it (readlink -f, note that this will follow all links. If you only want the first level, remove the -f) and print the target along with (link). If it is not, it will just print $f.

  2. If this is just for you and not intended to be parsed, just use ls -l:

    $ ls -l
    total 512116
    -rw-r--r-- 1 terdon terdon    100641 Jun 30 19:10 er
    lrwxrwxrwx 1 terdon terdon         5 Jun 30 19:12 etc -> /etc/
    -rw-r--r-- 1 terdon terdon 524288000 Jun 30 19:10 foo
    -rwxr--r-- 1 terdon terdon       353 Jun 30 15:22 sample.R
    -rwxr--r-- 1 terdon terdon       249 Jun 30 14:51 sample.R~
    

    That will clearly indicate links with link -> target.

  • I edited my question would you give a second look? I can't use the default arrow (->) on the output of ls -l. Is it possible to simulate my original command output with shell? – Felipe Leão Jun 30 '14 at 17:34
  • @FelipeLeão yes, and you really don't want to do it with ls since that will break on file names with newlines or other strange characters. Please edit your question and clarify precisely what information you need. – terdon Jun 30 '14 at 17:45
  • Updated the Question! – Felipe Leão Jun 30 '14 at 20:06

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.