什么是 Shell
Shell 是 Linux 的命令解释程序,是用户和内核之间的接口。除了作为命令解释程序外,Shell 同时还提供了一个可支持强大脚本语言的程序环境。
Bourne Shell (sh),是 Unix 系统的默认 Shell,简单轻便,脚本编程功能强,但交互性差。
Bourne Again Shell,即 Bash,是 GNU 开发的一个 Shell,也是大部分 Linux 系统的默认 Shell,是 Bourne Shell 的扩展。
许多 Bash 脚本会在文件首行加上 #!/bin/bash 。这里 #! 符号的名称是 shebang(也叫 sha-bang,即 sharp # 与 bang !)。当一个文本文件首行有 shebang,且以可执行模式执行时,shebang 后的内容会看作这个脚本的解释器和相关参数,系统会执行解释器命令,并将脚本文件的路径作为参数传递给该命令。
Bash 也支持在同一个行中安排多个命令:
组命令:
像大多数程序设计语言一样,shell 也允许用户在程序中使用变量。但 shell 不支持数据类型,它将任何变量值都当作字符串。但从赋值形式上看,可将 shell 变量分成四种形式:用户自定义、环境变量、位置变量和预定义特殊变量。
变量定义:name=串,其中 = 两边不允许有空格。如果字串中含空格,就要用双引号括起。在引用时,使用 $name 或 ${name},后者花括号是为了帮助解释器识别变量边界。
已定义的变量可以通过 unset name 来删除。
每个用户登录系统后,Linux 都会为其建立一个默认的工作环境,由一组环境变量定义,用户可以通过修改这些环境变量,来定制自己工作环境。在 Bash 中,可用 env 命令列出所有已定义的环境变量。通常,用户最关注的几个变量是:
可以使用 export 命令来定义环境变量。在同一个 shell 中使用 export 定义之后,这个环境变量会一直保留,直到这个 shell 退出
此外,也可以在命令前加上环境变量的定义。此时只有这一条命令的环境变量出现变化。
例子:
$ set one two three
$ echo $1,$2,$3
one,two,three
$ shift 2
$ echo $1,$2,$3
three,,
$ # 此时 $2 和 $3 已不存在Shell 中还有一组有 shell 定义和设置的特殊变量,用户只能引用,而不能直接改变或重置这些变量。
特殊字符示例
$ ls /mnt/c/Program Files/
ls: cannot access /mnt/c/Program: No such file or directory
ls: cannot access Files/: No such file or directory
$ # 对于 ls 来说,它接收到了两个参数:/mnt/c/Program 和 Files/,因此会报错。
$ # 可以使用反斜杠来转义空格
$ ls /mnt/c/Program\ Files/ # 输出省略
$ # 或者使用双引号或单引号包裹
$ ls "/mnt/c/Program Files/"
$ ls '/mnt/c/Program Files/'
$ echo "$PWD" # 双引号中仍然可以使用各种 shell 特殊符号
/home/ustc
$ echo '$PWD' # 但是单引号则不行
$PWD
$ ls -lh `which ls` # 查看 ls 命令对应的程序信息,使用反引号语法
-rwxr-xr-x 1 root root 139K Sep 5 2019 /usr/bin/ls
$ ls -lh $(which ls) # 使用 $(command) 语法也是一样的
-rwxr-xr-x 1 root root 139K Sep 5 2019 /usr/bin/ls可以使用 read 命令读取用户输入,并将输入的内容赋值给变量。其中 -p 参数后可以设置输出的提示信息。
$ name=""
$ read -p "Enter your name: " name # 输出提示,然后从输入读取一个值,存储到 $name 中
Enter your name: linux
$ echo $name
linux在使用 read 时,建议加上 -r 参数,否则 \ 会被视为转义符号。
$ message=""
$ read -p "Enter your message: " message
Enter your message: \(^o^)/~
$ echo $message # 可以看到,反斜杠被认为是转义符号,结果被丢弃
(^o^)/~
$ read -r -p "Enter your message: " message
Enter your message: \(^o^)/~
$ echo $message # 加上 -r 参数后,反斜杠完好无损
\(^o^)/~可以使用 echo 命令输出变量信息。其中 -n 参数不会在结尾输出换行符,而 -e 参数会解析文本中的转义字符(例如 \n)。
$ echo -n "hello"
hello$ # 由于这里 echo 结尾不输出换行,shell 就会在这里继续接受用户输入
$ # 这也是为什么在 C 语言中最后的 printf 输出需要加上 \n 的原因
$ name="linux"
$ echo "Hello $name.\nWelcome to bash!" # 可以看到 \n 没有被转义成换行
Hello linux.\nWelcome to bash!
$ echo -e "Hello $name.\nWelcome to bash!" # 加上 -e 之后就被转义了
Hello linux.
Welcome to bash!此外,printf 命令也可以用来输出,它的使用方法类似于 C 中的 printf() 函数。
$ name="linux"
$ printf "Hello %s" "$name"
Hello linux$ # 和 echo 不同,printf 结尾默认不输出换行符
$ printf "Hello %s\n" "$name"
Hello linux
$ # 所以为了正常显示,需要在结尾补上 \n在 Bash 中进行算术运算,需要使用 expr 计算算术表达式值或 let 命令赋值表达式值到变量。基本运算符是 +、-、* (转义)、/、%。在 expr 中,运算符两边与操作数之间必须有空格,小括号要转义;但 let 则没有这个要求,运算符前后有无空格均可,小括号不需转义,但 = 前后不能有空格。
另外,所有标准的 shell 都支持另一种语法 (( 表达式 )),其中 表达式 是一个 C 风格的数学表达式,可以计算,也可以赋值。(( 表达式 )) 是一条完整的命令,命令的返回值为 0 或 1。若表达式的结果非零,那么 (( 表达式 )) 命令返回零,而当表达式结果为零时命令返回 1。
$ expr length "ustclug"
7
$ let a=0
$ echo $a
0
$ let a++
$ echo $a
1
$ ((a+=1))
$ echo $a
2
$ echo $((a+=a/a)) #使用 $(( 表达式 )) 可以将计算结果用作为命令行的一部分,就像使用变量一样。
3条件表达式写成 test 条件表达式,或 [ 条件表达式 ],注意表达式与方括号之间有空格。
表达式:int1 [option] int2,其中的参数可以用下列替换。
序列中可嵌套 if 语句,在 shell 中也允许有多个 elif ,但 shell 的流程控制不可为空。末尾的 fi 就是 if 倒过来写,后面还会遇到类似的结束符。
if condition1
then
commands1
elif condition2
then
commands2
else
commands3
fi选项值必须以右括号 ) 结尾,若匹配多个离散值,用 | 分隔。这里的 esac 也是 case 倒着写。
case <variable> in
value1|value2)
command1
command2
;;
value3)
command3
;;
*)
command4
;;
esacfor 循环可以对一个列表中的每个值都执行一系列命令。
for var in list
do
commands $var
done
``
### 条件循环:while 和 until
while 循环用于不断执行一系列命令,命令通常为测试条件。until 与 while 相反,仅在测试条件失败时循环。
```bash
while condition
do
commands
done
until condition
do
commands
done除此之外,用于流程控制的还有在 C 语言中同样常见的 break 和 continue。与 C 语言不同的是,它们还接受一个数字作为参数,即 break n 和 continue n,其中参数 n 均表示跳出或跳过 n 层循环。
与其他编程语言类似,shell 也可以定义函数。其定义格式为:
# POSIX syntax
name() {
commands
[return <int>]
}
# Bash syntax
function name {
commands
[return <int>]
}其中函数的参数可以像命令一样通过 2、$3 等获取,返回值可以显式用 return 返回,或以最后一条命令运行结果作为返回值。在函数中使用 return 会结束本次函数执行,而使用 exit 会直接结束退出包含函数的当前脚本程序。
函数在使用前必须定义,因此应将函数定义放在脚本开始的部分。在调用函数时仅使用其函数名即可。
Linux 101