Linux command line 5 - Seeing the World as the Shell Sees It
This article mainly references the book The Linux Command Line (2nd Edition). The shell tool used is Xshell, and the operating system is CentOS 7.6. This means that some shortcuts may not work in other shell tools, and there may be slight differences in files across different Linux operating systems. Please analyze based on your actual situation.
Since CentOS 7.6 is EOL, consider using Rocky Linux or Alma Linux instead.
Viewing the World from the Shell’s Perspective
Character Expansion
Each time you input a command and press the Enter key, Bash performs some text substitution operations before executing the command.
We’ve already encountered a few examples where simple characters like * have various meanings in the Shell. This process of handling is called expansion.
The content we input is expanded into other content before being executed by the Shell. To illustrate this, let’s take a look at the echo command.
echo: Displays a line of text.
1 | [root@lht ~]# echo this is a test |
The arguments passed to the echo command are displayed one by one. Let’s look at another example:
1 | [root@lht ~]# echo * |
Why was * not outputted? Recall the usage of wildcards: * means to match any number of characters in a filename, but we haven’t discussed how the Shell implements that functionality.
The simple answer is: before executing the echo command, the Shell expands * into other content (in this case, the filenames in the current working directory). When you press Enter, the Shell automatically expands all qualifying characters in the command line before executing the command.
Therefore, the echo command never receives *; it only receives the expanded result. Knowing this, we can see that the behavior of the echo command is entirely as expected.
Pathname Expansion
The working mechanism of wildcards is called pathname expansion.
Here are some examples:
1 | [root@lht ~]# ls |
You can even execute the following command:
1 | [root@lht ~]# echo *[[:lower:]] |
This displays files that end with a lowercase letter.
Or check the contents of another directory:
1 | [root@lht usr]# echo /usr/*bin |
Have you noticed that using the wildcard
*does not list hidden files? If you want to list all hidden files but don’t want the result to include.and.., which represent the current and parent directories, you can write:
1 echo .[!.]*This will display files where the first character is
., but the second character is not., which can handle most hidden files (though it won’t show files starting with multiple dots).The
Aoption of thelscommand can correctly list hidden files:
1 ls -A
Tilde Expansion
As we mentioned when introducing the cd command, the tilde ~ has a special meaning.
When it precedes a word, it expands to the home directory of the named user. If no word is specified, it expands to the current user’s home directory:
1 | [root@lht home]# echo ~ |
Arithmetic Expansion
Shell performs arithmetic operations through expansion, as illustrated below:
1 | [root@lht example]# echo $((2 + 2)) |
Format:
1 | $((expression)) |
Here, expression is an arithmetic expression composed of numbers and arithmetic operators.
Arithmetic expressions only support integers (not decimals), but they can perform various operations.
Here are some of the supported arithmetic operators:
| Operator | Description |
|---|---|
| + | Addition |
| - | Subtraction |
| * | Multiplication |
| / | Division (results in an integer) |
| % | Modulus (remainder) |
| ** | Exponentiation |
In arithmetic expressions, spaces are not significant. For example, five squared multiplied by three:
1 | [root@lht example]# echo $((5**2*3)) |
Here’s an example using division and the modulus operator. Note the result of integer division:
1 | [root@lht example]# echo Five divided by two equals $((5/2)) |
We will discuss arithmetic expressions in more detail later.
Brace Expansion
You can create multiple text strings according to the pattern within braces:
1 | [root@lht example]# echo Front-{A,B,C}-Back Front-A-Back |
The patterns used for brace expansion can include a prefix (preamble) and a suffix (postscript).
Brace expressions themselves can be a list of comma-separated strings or a range of integers or single characters, but they cannot contain unquoted whitespace characters. Here’s an example using an integer range:
1 | [root@lht example]# echo Number_{1..5} |
In Bash 4.0 and above, integers can also be zero-padded:
1 | [root@lht example]# echo {01..15} |
Here’s a series of letters arranged in descending order:
1 | [root@lht example]# echo {Z..A} |
Brace expansion can also be nested:
1 | [root@lht example]# echo a{A{1..4},B{5..8}}b |
So, what is the use of brace expansion? A common application is to create lists of files or directories.
Suppose you are a photographer with a large number of photos and want to organize them by year and month. The first thing to do is create a series of directories with a “year-month” naming format.
This way, the directories will be arranged in chronological order. You could manually type and create the entire directory list, but that would be tedious and error-prone. Instead, we can do it this way:
1 | [root@lht ~]# mkdir photos |
Parameter Expansion
Here, we will briefly introduce parameter expansion, which will be explained in more detail later when discussing shell scripts, as this feature is more practical in shell scripting than in command-line use.
Its many functions are related to the system’s ability to store a small amount of data and name various pieces of data. Several data elements (more accurately called variables) are available for users to view.
For example, your username can be stored in a variable named USER. To perform parameter expansion and display the content of USER, you can use the following command:
1 | [root@lht ~]# echo $USER |
If you want to see a list of available variables, you can do it this way:
1 | [root@lht ~]# printenv | less |
If you mistype the variable name, the output will be an empty string:
1 | [root@lht ~]# echo $SUER |
Command Substitution
You can use the output of a command as the content for expansion:
1 | [root@lht ~]# echo $(ls) |
In practical use, you might do the following:
1 | [root@lht ~]# ll $(which cd) |
This allows you to retrieve the detailed information of the cd command without knowing its full path.
The command mentioned in the original text:
1 ls -l $(which cp)In practice, it results in an error:
1
2
3 [root@lht ~]# ll $(which cp)
ls: invalid option -- '''
Try 'ls --help' for more information.The error message indicates an illegal option
'. To check thecpcommand and see thecdcommand:
1
2
3
4
5 [root@lht ~]# which cp
alias cp='cp -i'
/usr/bin/cp
[root@lht ~]# which cd
/usr/bin/cdIt shows that the
cpcommand is actuallycp -i. Since it contains a single quote, thelscommand reports an illegal option, while thecdcommand is just a path, allowing it to run normally.It is speculated that the original text’s
which cpoutput was/bin/cp, which may be due to differences in system versions. This will not be explored further.
Command substitution is not limited to simple commands; entire pipelines can be used for substitution.
1 | [root@lht ~]# file $(ls /bin/* | grep zip) |
In older versions of Shell, there is another syntax for command substitution that Bash also supports. In this syntax, backticks (```) are used instead of $(...):
1 | [root@lht ~]# ll `which cd` |
Quoting
As mentioned earlier, Shell has multiple expansion methods, and now we’ll discuss how to control these expansions.
Let’s look at a few examples:
1 | [root@lht ~]# echo this is a test |
In the first example, Shell uses word splitting to remove the extra spaces in the parameter list of the echo command. In the second example, parameter expansion replaces $1 with an empty string because 1 is an undefined variable.
Shell provides a mechanism called quoting to selectively prevent unwanted expansions.
Double Quotes
The first type of quoting is double quotes. If text is placed within double quotes, all special characters used by Shell lose their special meaning and are treated as ordinary characters, except for $ (dollar sign), \ (backslash), and ` (backtick).
This means that word splitting, pathname expansion, tilde expansion, and brace expansion are all disabled, but parameter expansion, arithmetic expansion, and command substitution remain available.
With double quotes, we can handle filenames that contain whitespace. For instance, if we encounter a file named two words.txt, using that filename on the command line would cause word splitting to treat it as two separate arguments instead of the single argument we want:
1 | [root@lht ~]# ll two words.txt |
By using double quotes, we prevent word splitting and obtain the desired result. Moreover, we can even fix the error:
1 | [root@lht ~]# ll "two words.txt" |
Remember, within double quotes, parameter expansion, arithmetic expression expansion, and command substitution remain effective:
1 | [root@lht ~]# echo "$USER $((2+2)) $(cal)" |
Earlier, we mentioned that word splitting removes extra spaces. Now let’s see how this works:
1 | [root@lht ~]# echo this is a test |
By default, word splitting treats spaces, tabs, and newlines as delimiters between words, meaning that unquoted spaces are not considered text but merely as separators.
In the above command, we have one command and four parameters; if we add double quotes, the spaces between the words are no longer treated as delimiters but as part of the parameters:
1 | [root@lht ~]# echo "this is a test" |
Note that newline characters are treated as delimiters by the word-splitting mechanism, which has subtle implications for command substitution. Consider the following example:
1
2
3
4
5
6
7
8
9
10 [root@lht ~]# echo $(cal)
June 2023 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
[root@lht ~]# echo "$(cal)"
June 2023
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30In the example above, unquoted command substitution results in the output’s spaces and newlines being treated as delimiters, whereas using the quoting format produces correctly formatted output.
Single Quotes
To disable all expansions, single quotes should be used. The following compares the effects of no quotes, single quotes, and double quotes:
1 | [root@lht ~]# echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER |
As the level of quoting increases, more expansions are disabled.
Escape Characters
Sometimes, we only want to quote a single character. To do this, we can precede the character with a backslash \, which is also known as an escape character. In double quotes, escape characters are often used to selectively avoid expansions:
1 | [root@lht ~]# echo "The balance for user $USER is: \$5.00" |
Special characters that may appear in filenames (including $, !, &, and spaces) can have special meanings for Shell. To correctly include such special characters in a filename, escape characters are required:
1 | [root@lht ~]# mv bad\&filename good_filename |
If you want to input a backslash \, you need to use an escape character to escape itself, like this: \\.
1 | [root@lht ~]# echo \\ |
In addition to being used as an escape character, a backslash can also be used to describe certain special characters known as control codes.
The first 32 characters in the ASCII encoding scheme are used to send commands to devices like teletypes. Some of these characters are quite familiar (such as tab, backspace, newline, and carriage return), while others are used less frequently.
Escape Sequence Meaning \a Alert (bell) \b Backspace \n New line \r Carriage return \t Tab The idea of using backslashes to describe escape sequences comes from the C language and has been adopted by many other programming languages, including Shell. The
-eoption forechocan interpret escape sequences. You can also place it within$' 'to achieve similar results. The following example uses thesleepcommand, which waits for a specified amount of time (in seconds) before exiting, creating a simple countdown timer:
1
2 [root@lht ~]# sleep 10; echo -e "Time's up\a"
Time's upAlternatively, you can do it this way:
1
2 [root@lht ~]# sleep 10; echo "Time's up" $'\a'
Time's up
- Title: Linux command line 5 - Seeing the World as the Shell Sees It
- Author: LHT
- Created at : 2023-01-05 08:00:00
- Link: https://blog.327774.xyz/2023/01/05/linux/Linux command line 5 - Seeing the World as the Shell Sees It/
- License: This work is licensed under CC BY-NC-SA 4.0.