Historically, the first Unix filesystem created two entries in every directory: .
pointing to the directory itself, and ..
pointing to its parent. This provided an easy way to traverse the filesystem, both for applications and for the OS itself.
Thus each directory has a link count of 2+n where n is the number of subdirectories. The links are the entry for that directory in its parent, the directory's own .
entry, and the ..
entry in each subdirectory. For example, suppose this is the content of the subtree rooted at /parent
, all directories:
/parent
/parent/dir
/parent/dir/sub1
/parent/dir/sub2
/parent/dir/sub3
Then dir
has a link count of 5: the dir
entry in /parent
, the .
entry in /parent/dir
, and the three ..
entries in each of /parent/dir/sub1
, /parent/dir/sub2
and /parent/dir/sub3
. Since /parent/dir/sub1
has no subdirectory, its link count is 2 (the sub1
entry in /parent/dir
and the .
entry in /parent/dir/sub1
).
To minimize the amount of special-casing for the root directory, which doesn't have a “proper” parent, the root directory contains a ..
entry pointing to itself. This way it, too, has a link count of 2 plus the number of subdirectories, the 2 being /.
and /..
.
Later filesystems have tended to keep track of parent directories in memory and usually don't need .
and ..
to exist as actual entries; typical modern unix systems treat .
and ..
as special values as part of the filesystem-type-independent filesystem code. Some filesystems still include .
and ..
entries, or pretend to even though nothing appears on the disk.
Most filesystems still report a link count of 2+n for directories regardless of whether .
and ..
entries exist, but there are exceptions, for example btrfs doesn't do this.
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 find
1 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.
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
.
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
.
Best Answer
The
l
you're referencing means the file is a link (symbolic) to another file (or directory).Example
These were created with a command,
ln -s source link
. The source is the file/directory we want to link to, the "link" is the name we want to give the link.making a link
confirming
Further details
If you consult the
info ls
info page you'll find descriptions of all the symbols used in the output ofls
.excerpt
References
10.1.2 What information is listed - coreutils documentation on ls