1. Overview

In this tutorial, we’ll explore the depths of the ls command, a foundational tool in UNIX and Linux systems. Beyond its basic usage, we’ll also highlight the differences between some common variations such as ls *, ls **, and ls ***.

Each command variation provides a distinct method for displaying directory contents. With a firm grasp of each, we can traverse and display files and subdirectories in any directory.

In certain scenarios, the Bash and Zsh shells may yield differing outputs. Throughout this tutorial, we’ll highlight and understand these variations wherever they arise.

2. Sample Directory Structure

The examples showcased in this article utilize a common directory hierarchy:

documents/ 
    report.docx 
    notes.txt 
photos/ 
    family.jpg 
    vacation/ 
        beach.jpg 
        mountain.jpg 
links_to_external/ -> /external_path/data/
    external1.txt
    external2.doc
code.py (Last modified on Aug 25 15:00) 
readme.md (Last modified on Aug 24 14:00)

In essence, we have two directories with some files, as well as one directory below (vacation), along with the symbolic link file links_to_external/. In this case, the symbolic link points to a directory, serving as a reference or shortcut to its target.

3. The ls Command

The ls command is one of the most frequently used commands in Linux and other UNIX-like operating systems. Its name stands for list and reveals its primary function – to list the contents of a directory. The core ls command exhibits the same behavior in both the Bash and Zsh shells.

When executed without any arguments, ls lists the files and subdirectories within the current directory:

$ ls
documents/ 
photos/
links_to_external/
code.py 
readme.md

From the output above, we can observe the straightforward nature of the ls command, providing a clear distinction between files and directories via the forward slash postfix. Moreover, the ls command alone only displays the symbolic link itself and not the contents of what it links to.

4. Common ls Switches

With various options of ls like -l, -lh, and -t, users can customize their view to display detailed file information, present sizes in a comprehensible format, or sort files by modification time.

Now, let’s move on by exploring some of these frequently used switches of the ls command.

4.1. The -l Switch

By using the -l option with ls, we can display additional details about each file:

$ ls -l
drwxr-xr-x 2 user user 4096 Aug 24 12:00 documents
lrwxrwxrwx 1 user user 20 Aug 24 11:55 links_to_external -> /external_path/data/
drwxr-xr-x 2 user user 4096 Aug 24 12:00 photos
-rw-r--r-- 1 user user 2048 Aug 25 15:00 code.py
-rw-r--r-- 1 user user 1024 Aug 24 14:00 readme.md

These include data permissions, number of hard links, owner, group, file size, and last modification date.

4.2. The -lh Switch

By using the -lh option with ls, we can display the same details as the -l option but with file sizes presented in a human-readable format:

$ ls -lh
drwxr-xr-x  2 user user 4.0K Aug 24 12:00 documents
lrwxrwxrwx  1 user user   20 Aug 24 11:55 links_to_external -> /external_path/data/
drwxr-xr-x  2 user user 4.0K Aug 24 12:00 photos
-rw-r--r--  1 user user 2.0K Aug 25 15:00 code.py
-rw-r--r--  1 user user 1.0K Aug 24 14:00 readme.md

For example, the value 4096 is converted to 4.0K.

4.3. The -t Switch

Using the -t (time) command sorts the list for us by modification time, with the newest first:

$ ls -t
code.py  
readme.md  
photos/  
documents/
links_to_external/

This sorting is often useful when sorting files for deletion such as when organizing backups.

4.4. The -F Switch

The -F switch appends a character to each entry to indicate its type. Directories get a /, symbolic links get an @, executables (like scripts or binaries with execution permissions) get a *, and so on:

$ ls -F
documents/
links_to_external@
photos/
code.py*

In this case, code.py is assumed to be an executable (hence the *), while documents and photos are directories (hence the /).

4.5. The -u Switch

The -u switch causes ls to sort and display entries based on access time rather than modification time. Access time changes whenever the file is read:

$ ls -u
code.py 
readme.md 
photos/ 
documents/
links_to_external/

Without also using -l or another option that displays times, we might not notice a difference in a simple listing.

5. ls * Command

In UNIX-like shells, the ls * command utilizes the asterisk (*) as a wildcard. This wildcard matches any number of characters in file or directory names, effectively expanding to all files and directories in the current directory.

The behavior of the ls * command in Zsh is largely similar to that in Bash due to the globbing feature.

In essence, the ls * command lists all files and directories in the current directory. Still, the command doesn’t include directories that begin with a dot (hidden files):

$ ls *
code.py
documents/
links_to_external/ -> /external_path/data/
photos/
readme.md

As shown above, the ls * command presents all visible files and directories in the current directory. Unlike the ls command, the ls * command displays the linked-to directory /external_path/data/.

6. The ls ** Command

In UNIX-like shells, the use of double asterisks (**) is also interpreted via globbing:

  • the first asterisk matches any file or directory
  • the second asterisk might enable recursive searching, delving into subdirectories, and below

Essentially, it enables us to explore the entire directory tree starting from the current directory, as long as the shell supports this feature.

6.1. ls ** Command in Bash

In Bash, by default, the ls ** command behaves the same way as ls *, listing all files and directories in the current directory but not performing a recursive search:

$ ls **
code.py
documents/
links_to_external/ -> /external_path/data/
photos/
readme.md

In other words, the ls ** command in Bash mirrors the simpler ls * command’s output.

6.2. ls ** Command in Zsh

In some advanced shells like Zsh, the ls ** command not only lists the files and directories in the current directory but also recursively lists files in subdirectories, the directories below them, and so on:

$ ls **
code.py
documents/report.docx
documents/notes.txt
links_to_external/ -> /external_path/data/
photos/family.jpg
photos/vacation/beach.jpg
photos/vacation/mountain.jpg
readme.md

In summary, the ls ** command provides a recursive listing of files and directories in advanced shells like Zsh. However, it still doesn’t show the subdirectories of the symbolic link.

7. The ls *** Command

The ls *** command might be a bit puzzling initially because the additional asterisks (*) in traditional globbing patterns in shells like Bash don’t offer any additional specificity.

7.1. Bash

In Bash, there’s no native distinction between *, **, or *** in terms of pattern matching. All of them are seen as a wildcard that matches any string of characters in file or directory names within the current directory level.

Thus, using ls *** in Bash lists all files and directories in the current directory, without recursive traversing:

$ ls ***
code.py
documents/
links_to_external/ -> /external_path/data/
photos/
readme.md

On the other hand, other shells can again behave differently.

7.2. Zsh

When you run ls *** in Zsh, it lists files and directories recursively from the current directory, similar to ls **, but with the added capability of following and listing the contents of symbolic links to directories. As mentioned earlier, a symbolic link is a file that points to another file or directory, serving as a reference or shortcut to its target:

code.py
documents/
documents/report.docx
documents/notes.txt
links_to_external/ -> /external_path/data/
links_to_external/external1.txt
links_to_external/external2.doc
photos/
photos/family.jpg
photos/vacation/
photos/vacation/beach.jpg
photos/vacation/mountain.jpg
readme.md

Notably, unlike all previous commands, when listing the contents of the symbolic link links_to_external using the ls *** in the Zsh shell, it reveals the referenced files external1.txt and external2.doc.

8. The ls [text]* Command

When executing the command ls [text]*, we’re instructing the system to list items within the current directory whose names start with the string [text]. Importantly, text shouldn’t contain any forward slashes, as that would cause it to be interpreted as a path.

This command allows us to quickly filter and view files and directories based on a common prefix:

$ ls doc*
documents/

The ls [text]* command specifically targets items named [text] and lists all objects in the current directory that match that filter.

9. Conclusion

In this article, we examined the ls command, its variants (ls *, ls **, and ls ***), along with the ls [directory]* format across UNIX and Linux systems. While the behavior of the asterisk variation is consistent in Bash, there are distinct recursive features in Zsh for the ls ** and ls *** commands.

Additionally, we shed light on several basic switches that enhance the command’s capabilities. With this knowledge, one can navigate directories more efficiently, especially when transitioning between Bash and Zsh shells.