Shell 简介
Bash(Bourne Again SHell) 是 Unix 系统和 Linux 系统的一种命令环境。
Shell 是一个程序,它提供了用户与计算机交互的环境。其次,Shell 也是命令解释器,能够写一些简单的脚本。最后,Shell 也是一个工具箱,方便用户操作系统。
我们可以查看在 Linux 系统下的 Shell。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $SHELL
/bin/bash
[root@iZ7xvfomazz4187zib4aurZ ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash
命令行环境
进入命令行环境以后,用户会看到 Shell 的提示符。
# 用户名@主机名
[user@hostname] $
当然,如果用户是 root
那么会以 #
结尾,而不是以美元符号 $
结尾。
一般来说,进入命令行环境后就已经打开 Bash 了,如果不是则可以用 bash
来启动,然后退出就是 exit
命令。
Bash 基本语法
echo 命令
echo
命令就相当于 print
命令的用法。
# 单行文本输出
[root@iZ7xvfomazz4187zib4aurZ ~]# echo Hello Yikuanzz
Hello Yikuanzz
# 多行文本输出
[root@iZ7xvfomazz4187zib4aurZ ~]# echo "<HTML>
> <HEAD>
> <TITLE>Page Title</TITLE>
> </HEAD>
> <BODY>
> Page body.
> </BODY>
> </HTML>"
<HTML>
<HEAD>
<TITLE>Page Title</TITLE>
</HEAD>
<BODY>
Page body.
</BODY>
</HTML>
-n
参数
默认情况下,echo
输出的文本末尾会有一个回车符号,-n
参数可以取消末尾的回车符。分号作为命令的结束符号。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo -n Hello Yikuanzz
Hello Yikuanzz[root@iZ7xvfomazz4187zib4aurZ ~]#
[root@iZ7xvfomazz4187zib4aurZ ~]# echo hello;echo yikuanzz
hello
yikuanzz
-e
参数
-e
参数会解释引号里面的特殊字符。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo "Hello\nYikuanzz"
Hello\nYikuanzz
[root@iZ7xvfomazz4187zib4aurZ ~]# echo -e "Hello\nYikuanzz"
Hello
Yikuanzz
shopt 命令
shopt
命令用来调整 Bash 的行为:
shopt -s [optionname]
打开某个参数。shopt -u [optionname]
打开某个参数。shopt [optionname]
查询某个参数是关闭还是打开
dotglob 参数
dotglob
参数可以让拓展结果包括隐藏文件(即 .
开头的文件)。
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt dotglob
dotglob off
[root@iZ7xvfomazz4187zib4aurZ ~]# ls *
file.txt
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt -s dotglob
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt dotglob
dotglob on
[root@iZ7xvfomazz4187zib4aurZ ~]# ls *
.bash_history .bashrc .lesshst file.txt
nullglob 参数
nullglob
参数可以让通配符不匹配任何文件名时,返回空字符。
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt nullglob
nullglob off
[root@iZ7xvfomazz4187zib4aurZ ~]# rm b*
rm: cannot remove ‘b*’: No such file or directory
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt -s nullglob
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt nullglob
nullglob on
[root@iZ7xvfomazz4187zib4aurZ ~]# rm b*
rm: missing operand
Try 'rm --help' for more information.
failglob 参数
failglob
参数让通配符不匹配任何文件名时,Bash 会直接报错,而不是让命令去处理。
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt -s failglob
[root@iZ7xvfomazz4187zib4aurZ ~]# rm b*
-bash: no match: b*
extglob 参数
extglob
参数让 Bash 支持 Korn SHell 的拓展语法,主要是量词语法。
量词语法有下面几个:
?(pattern-list)
:匹配零个或一个模式;例如?(def)
表示匹配零个或一个def
。*(pattern-list)
:匹配零个或多个模式.+(pattern-list)
:匹配一个或多个模式;例如+(.txt|.php)
表示匹配.txt
或.php
。@(pattern-list)
:只匹配一个模式。!(pattern-list)
:匹配零个或一个以上的模式,但不匹配单独一个的模式。
nocaseglob 参数
nocaseglob
参数可以让通配符拓展不区分大小写。
比如,打开后 program*
就不区分大小写了,可以匹配 ProgramData
等等。
globstar 参数
globstar
参数可以让 **
匹配零个或多个子目录。
比如,我们用 ls **/*.txt
就可以匹配当前目录和多个子目录的 txt
文件。
read 命令
read
命令,它将用户的输入存入一个变量,它的语法格式为 read [-options][variable]
。其中,options
是参数选项,variable
是用来保存输入数值的一个或多个变量名,如果没有提供变量名,环境变量 REPLY
会包含用户输入的一整行数据。
#!/usr/bin/env bash
echo -n "输入文本 >"
read text
echo "你的输入为: $text"
read
命令除了读取键盘输入,可以用来读取文件。
#!/usr/bin/env bash
while read myline
do
echo "$myline"
done < $filename
-t
参数
read
命令的 -t
参数,设置了超时的秒数。如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行。或者我们也可以设置环境变量 TMOUT
。
#!/usr/bin/env bash
echo -n "输入文本 > "
if read -t 3 response; then
echo "用户已输入"
else
echo "用户未输入"
fi
-p
参数
read
命令的 -p
参数,指定用户输入的提示信息。
#!/usr/bin/env bash
read -p "Enter one or more values > "
echo "REPLY = '$REPLY'"
-a
参数
read
命令的 -a
参数把用户的输入赋值给一个数组,索引是从 0 开始的。
[root@iZ7xvfomazz4187zib4aurZ bin]# read -a response
Hello Yikuanzz
[root@iZ7xvfomazz4187zib4aurZ bin]# echo ${response[1]}
Yikuanzz
-n
参数
read
命令的 -n
参数指定只读取若干字符作为变量值,而不是整行读取。
[root@iZ7xvfomazz4187zib4aurZ ~]# read -n 3 var
aklhgalg
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $var
akl
-e
参数
read
命令的 -n
参数允许用户输入的时候,使用 readline
库提供的快捷键。
#!/usr/bin/env bash
echo "请输入文件地址:"
read -e fileName
echo $fileName
IFS 变量
read
命令读取的值,默认是以空格分隔,可以通过自定义环境变量 IFS
(内部字段分隔符,Internal Field Separator ),修改分隔标志。
[root@iZ7xvfomazz4187zib4aurZ ~]# IFS='|'
[root@iZ7xvfomazz4187zib4aurZ ~]# text='aa|bbb|cccc'
[root@iZ7xvfomazz4187zib4aurZ ~]# for i in $text;do echo "i=$i"; done
i=aa
i=bbb
i=cccc
如果 IFS
设为空字符串,整行读入一个边量。
#!/usr/bin/env bash
input="/path/to/txt/file"
while IFS= read -r line
do
echo "$line"
done < "$input"
条件判断 if
和 case
if
、elif
和 else
关键字。
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
一个小例子:
#!/usr/bin/env bash
echo -n "输入一个1到3之间的数字(包含两端)>"
read character
if [ "$character" = "1" ]; then
echo 1
elif [ "$character" = "2" ]; then
echo 2
elif [ "$character" = "3" ]; then
echo 3
else
echo "输入不符合要求"
fi
在 if
结构的判断条件中,一般使用 test
命令,有三种形式:test expression
、[ expression ]
、[[ expression ]]
。
另一个常见的条件判断就是 case
结构了。
case expression in
pattern )
commands ;;
pattern )
commands ;;
...
esac
一个小例子:
#!/usr/bin/env bash
echo -n "输入一个1到3之间的数字(包含两端)> "
read character
case $character in
1 ) echo 1
;;
2 ) echo 2
;;
3 ) echo 3
;;
* ) echo "输入不符合要求"
esac
文件判断
以下表达式用来判断文件状态。
[ -a file ]
:如果 file 存在,则为true
。[ -b file ]
:如果 file 存在并且是一个块(设备)文件,则为true
。[ -c file ]
:如果 file 存在并且是一个字符(设备)文件,则为true
。[ -d file ]
:如果 file 存在并且是一个目录,则为true
。[ -e file ]
:如果 file 存在,则为true
。[ -f file ]
:如果 file 存在并且是一个普通文件,则为true
。[ -g file ]
:如果 file 存在并且设置了组 ID,则为true
。[ -G file ]
:如果 file 存在并且属于有效的组 ID,则为true
。[ -h file ]
:如果 file 存在并且是符号链接,则为true
。[ -k file ]
:如果 file 存在并且设置了它的“sticky bit”,则为true
。[ -L file ]
:如果 file 存在并且是一个符号链接,则为true
。[ -N file ]
:如果 file 存在并且自上次读取后已被修改,则为true
。[ -O file ]
:如果 file 存在并且属于有效的用户 ID,则为true
。[ -p file ]
:如果 file 存在并且是一个命名管道,则为true
。[ -r file ]
:如果 file 存在并且可读(当前用户有可读权限),则为true
。[ -s file ]
:如果 file 存在且其长度大于零,则为true
。[ -S file ]
:如果 file 存在且是一个网络 socket,则为true
。[ -t fd ]
:如果 fd 是一个文件描述符,并且重定向到终端,则为true
。 这可以用来判断是否重定向了标准输入/输出错误。[ -u file ]
:如果 file 存在并且设置了 setuid 位,则为true
。[ -w file ]
:如果 file 存在并且可写(当前用户拥有可写权限),则为true
。[ -x file ]
:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
。[ file1 -nt file2 ]
:如果 FILE1 比 FILE2 的更新时间最近,或者 FILE1 存在而 FILE2 不存在,则为true
。[ file1 -ot file2 ]
:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
。[ FILE1 -ef FILE2 ]
:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true
。
字符串判断
以下表达式用来判断字符串。
[ string ]
:如果string
不为空(长度大于0),则判断为真。[ -n string ]
:如果字符串string
的长度大于零,则判断为真。[ -z string ]
:如果字符串string
的长度为零,则判断为真。[ string1 = string2 ]
:如果string1
和string2
相同,则判断为真。[ string1 == string2 ]
等同于[ string1 = string2 ]
。[ string1 != string2 ]
:如果string1
和string2
不相同,则判断为真。[ string1 '>' string2 ]
:如果按照字典顺序string1
排列在string2
之后,则判断为真。[ string1 '<' string2 ]
:如果按照字典顺序string1
排列在string2
之前,则判断为真。
整数判断
下面的表达式用于判断整数。
[ integer1 -eq integer2 ]
:如果integer1
等于integer2
,则为true
。[ integer1 -ne integer2 ]
:如果integer1
不等于integer2
,则为true
。[ integer1 -le integer2 ]
:如果integer1
小于或等于integer2
,则为true
。[ integer1 -lt integer2 ]
:如果integer1
小于integer2
,则为true
。[ integer1 -ge integer2 ]
:如果integer1
大于或等于integer2
,则为true
。[ integer1 -gt integer2 ]
:如果integer1
大于integer2
,则为true
。
正则判断
[[ expression ]]
支持正则表达式。
一般来说,它的语法格式是 [[ string1 =~ regex ]]
,其中 regex
是一个正则表达式,=~
是正则比较运算符号。
#!/usr/bin/env bash
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
echo "INT is an integer."
exit 0
else
echo "INT is not an integer." >&2
exit 1
fi
循环 while
、until
和for
首先要介绍的是每个语言中,几乎都会有的关键字 break
和 continue
。
break
命令立即终止循环,程序继续执行循环块之后的语句。
continue
命令立即终止本轮循环,开始执行下一轮循环。
while
循环
while condition; do
commands
done
一个小例子:
#!/bin/bash
number=0
while [ "$number" -lt 10 ]; do
echo "Number = $number"
number=$((number + 1))
done
until
循环
until condition; do
commands
done
因为 until
循环与 while
循环恰好相反,只要不符合判断条件,就不断循环执行指定的语句。
#!/usr/bin/env bash
number=0
until [ "$number" -ge 10 ]; do
echo "Number = $number"
number=$((number + 1))
done
until
的条件部分也可以是一个命令,表示命令执行成功之前,不断重复尝试。
until cp $1 $2; do
echo 'Attempt to copy failed. waiting ...'
sleep 5
done
如果我们写成 while
循环的形式,把条件设为否定。
while ! cp $1 $2; do
echo 'Attempt to copy failed. waiting ...'
sleep 5
done
for ... in
循环
for ... in
循环主要用于遍历列表的每一项。
for variable in list
do
commands
done
列表可以有字符串组成的列表、通配符产生的列表或者子命令执行产生的列表,我们可以看看下面的例子。
#!/usr/bin/env bash
# 字符串形式
for i in word1 word2 word3; do
echo $i
done
# 通配符形式
for i in *.png; do
ls -l $i
done
# 子命令形式
count=0
for i in $(car ~/.bash_profile); do
count=$((count+1))
echo "Word $count ($i) contains $(echo -n $i | wc -c) characters"
done
in list
部分可以省略,当省略后它相当于将脚本的所有参数 $@
并入循环,但是为了可读性我们不会希望将它省略。
#!/usr/bin/env bash
for filename in "$@"; do
echo "$filename"
done
for
循环
for (( expression1; expression2; expression3 )); do
commands
done
其中,expression1
用来初始化循环条件,expression2
用来决定循环结束的条件,expression3
在每次循环迭代的末尾执行,用于更新值。
#!/usr/bin/env bash
for (( i=0; i<5; i+=1 )); do
echo $i
done
select
结构
select
结构主要用来生成简单的菜单,它的语法与 for ... in
循环基本一致。
select name
[in list]
do
commands
done
Bash 会对select
依次进行下面的处理。
select
生成一个菜单,内容是列表list
的每一项,并且每一项前面还有一个数字编号。- Bash 提示用户选择一项,输入它的编号。
- 用户输入以后,Bash 会将该项的内容存在变量
name
,该项的编号存入环境变量REPLY
。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。 - 执行命令体
commands
。 - 执行结束后,回到第一步,重复这个过程。
当然,select
可以与 case
结合,针对不同项,执行不同的命令。
#!/usr/bin/env bash
echo "Which Operating System do you like?"
select os in Ubuntu LinuxMint Windows8 Windows7 WindowsXP
do
case $os in
"Ubuntu"|"LinuxMint")
echo "I also use $os."
;;
"Windows8" | "Windows10" | "WindowsXP")
echo "Why don't you try Linux?"
;;
*)
echo "Invalid entry."
break
;;
esac
done
命令组合符号 &&
和 ||
Command1 && Command2
表示 Command1
执行成功后,继续执行 Command2
。
[root@iZ7xvfomazz4187zib4aurZ ~]# cat file.txt && ls -l file.txt
hello
-rw-r--r-- 1 root root 6 Apr 23 09:39 file.txt
Command1 || Command2
表示 Command1
执行失败后,才执行 Command2
。
[root@iZ7xvfomazz4187zib4aurZ ~]# mkdir foo || mkdir bar
此外,我们通过 type
可以知道命令是内置命令还是外部命令。
[root@iZ7xvfomazz4187zib4aurZ ~]# type echo
echo is a shell builtin
[root@iZ7xvfomazz4187zib4aurZ ~]# type mkdir
mkdir is hashed (/usr/bin/mkdir)
如果,我们给 type
加上 -t
参数,就可以返回一个命令的类型:别名(alias)、关键词(keyword)、函数(function)、内置命令(builtin)和文件(file)。
[root@iZ7xvfomazz4187zib4aurZ ~]# type -t type
builtin
Bash 模式扩展
Shell 接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元(token)。然后,Shell 会扩展词元里面的特殊字符,扩展完成后才会调用相应的命令。
这种特殊字符的扩展,称为模式扩展(globbing)。其中有些用到通配符,又称为通配符扩展(wildcard expansion)。
我们可以通过 set -f
命令关闭拓展,set +f
命令打开拓展。
~
拓展
我们使用 ~
会自动拓展为当前用户的主目录。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ~
/root
用 ~/dir
表示拓展成主目录的某个子目录,注意该目录的实际地址并不一定在这个目录。
[root@iZ7xvfomazz4187zib4aurZ home]# echo ~/test
/root/test
用 ~user
表示拓展成用户 user
的主目录,如果该用户不存在则不会拓展。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ~root
/root
用 ~+
则会拓展为当前所在的目录。
[root@iZ7xvfomazz4187zib4aurZ test]# echo ~+
/home/test
?
拓展
?
代表文件路径里面的任意字符,这里是不包括空字符的。
[root@iZ7xvfomazz4187zib4aurZ test]# ls ??.txt
f1.txt f2.txt f3.txt
[root@iZ7xvfomazz4187zib4aurZ test]# echo ?.txt
a.txt b.txt
*
拓展
*
代表文件路径里面的任意数量字符,包括数量为零的情况。
[root@iZ7xvfomazz4187zib4aurZ test]# ll *.txt
-rw-r--r-- 1 root root 0 Apr 23 14:36 a.txt
-rw-r--r-- 1 root root 0 Apr 23 14:36 b.txt
-rw-r--r-- 2 root root 0 Feb 27 08:19 f1.txt
-rw-r--r-- 2 root root 0 Feb 27 08:19 f2.txt
lrwxrwxrwx 1 root root 6 Feb 27 08:28 f3.txt -> f1.txt
*
不会匹配隐藏文件,就是以 .
开头的文件。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo .*
. .. .bash_history .bash_logout .bash_profile .bashrc .cache .cshrc .docker .dotnet .lesshst .mysql_history .pip .pki .pydistutils.cfg .rnd .ssh .tcshrc .viminfo .vscode-server
[root@iZ7xvfomazz4187zib4aurZ ~]# echo .[!.]*
.bash_history .bash_logout .bash_profile .bashrc .cache .cshrc .docker .dotnet .lesshst .mysql_history .pip .pki .pydistutils.cfg .rnd .ssh .tcshrc .viminfo .vscode-server
[]
拓展
[]
表示选择 [...]
中的一个字符。
[root@iZ7xvfomazz4187zib4aurZ test]# echo [fab]*.txt
a.txt b.txt f1.txt f2.txt f3.txt
当然,我们可以使用 [^...]
和 [!...]
来匹配不在括号中的字符。
[root@iZ7xvfomazz4187zib4aurZ test]# echo ?[!3].txt
f1.txt f2.txt
注意,即使文件不存在也会进行拓展。与之相对,如果文件不存在,大部分的符号都不会拓展。
[start-end]
拓展
[start-end]
表示匹配一个连续的范围,比如 [0-5]
匹配 [012345]
。
常用的有:
[a-z]
:所有小写字母。[a-zA-Z]
:所有小写字母与大写字母。[a-zA-Z0-9]
:所有小写字母、大写字母与数字。
[root@iZ7xvfomazz4187zib4aurZ test]# echo f[1-3].txt
f1.txt f2.txt f3.txt
{}
拓展
{}
表示分别拓展成大括号里面的所有值,各个值之间用逗号分隔,比如 {1,2,3}
拓展为 1 2 3
。
[root@iZ7xvfomazz4187zib4aurZ test]# echo hello-yikuan{1,2,3,4,5}
hello-yikuan1 hello-yikuan2 hello-yikuan3 hello-yikuan4 hello-yikuan5
[root@iZ7xvfomazz4187zib4aurZ test]# echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b
[root@iZ7xvfomazz4187zib4aurZ test]# echo {cat,f*}
cat f1.txt f2.txt f3.txt
{start..end}
拓展
{start..end}
表示拓展成一个连续的序列。比如,{a..z}
可以扩展成26个小写英文字母。
[root@iZ7xvfomazz4187zib4aurZ test]# echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
[root@iZ7xvfomazz4187zib4aurZ test]# echo {001..11}
001 002 003 004 005 006 007 008 009 010 011
[root@iZ7xvfomazz4187zib4aurZ test]# echo {1..9..3}
1 4 7
常见的用途:
- 新建一系列目录:
mkdir {2007..2009}-{01..12}
。 - 用于
for
循环:for i in {1..4}
。
###
拓展
Bash 将美元符号 $
开头的词视为变量。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $SHELL
/bin/bash
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${SHELL}
/bin/bash
${!string*}
或 ${!string@}
返回所有给定字符串 string
的变量名。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${!S*}
SECONDS SHELL SHELLOPTS SHLVL SSH_CLIENT SSH_CONNECTION SSH_TTY
$(...)
拓展
$(...)
可以拓展为另外一个命令的运行结果。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $(ls $(pwd))
dockerPratic file.txt foo go minikube-1.31.0-bp156.1.10.x86_64.rpm mysql80-community-release-el7-5.noarch.rpm nginx snap testdir
$((...))
可以拓展成整数运算的结果。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $((1+2))
3
[[:class:]]
拓展
[[:class:]]
表示一个字符类,扩展成某一类特定字符之中的一个。常用的字符类如下。
[[:alnum:]]
:匹配任意英文字母与数字[[:alpha:]]
:匹配任意英文字母[[:blank:]]
:空格和 Tab 键。[[:cntrl:]]
:ASCII 码 0-31 的不可打印字符。[[:digit:]]
:匹配任意数字 0-9。[[:graph:]]
:A-Z、a-z、0-9 和标点符号。[[:lower:]]
:匹配任意小写字母 a-z。[[:print:]]
:ASCII 码 32-127 的可打印字符。[[:punct:]]
:标点符号(除了 A-Z、a-z、0-9 的可打印字符)。[[:space:]]
:空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。[[:upper:]]
:匹配任意大写字母 A-Z。[[:xdigit:]]
:16进制字符(A-F、a-f、0-9)。
Bash 引号和转义
Bash 的数据类型只有字符串,所以与字符串相关的引号和转义就很重要。
转义
一般我们用 \
来进行转义,当然,如果一些命令过长可以用 \
来进行换行。
\b
:退格。\n
:换行。\r
:回车。\t
:制表符。
单引号与双引号
与单引号不同的是,在双引号中,美元符号($
)、反引号(`` )和反斜杠(
`)会被自动拓展。
[root@iZ7xvfomazz4187zib4aurZ test]# echo "$(date)"
Wed Apr 24 23:50:35 CST 2024
因为双引号把换行符解释为普通字符,所以可以利用双引号在命令行输入多行文本。
[root@iZ7xvfomazz4187zib4aurZ test]# echo "hello
> Yikuanzz"
hello
Yikuanzz
此外,双引号一般用于处理文件名包含空格的文件。
当然,双引号还可以保存原始命令的输出格式。
[root@iZ7xvfomazz4187zib4aurZ test]# echo $(cal)
April 2024 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@iZ7xvfomazz4187zib4aurZ test]# echo "$(cal)"
April 2024
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
Here 文档
Here 文档(here document)是一种输入多行字符串的方法,它以 << token
作为开始标记,以 token
作为结束标记。
[root@iZ7xvfomazz4187zib4aurZ test]# << _EOF_
> Hello
> Yikuanzz
> _EOF_
注意,在 Here 文档内部会发生变量替换和通配符拓展,但是单引号和双引号都会失去语法作用。
如果你不想发生变量替换和通配符拓展,那么就把 token
用单引号引起来:'token'
。
Here 文档的一种变体:Here 字符串(Here string),使用三个小于号(<<<
)表示。
[root@iZ7xvfomazz4187zib4aurZ test]# cat <<< "Hello Yikuanzz"
Hello Yikuanzz
Bash 变量
Bash 变量主要有环境变量和自定义变量两种。
环境变量就是 Bash 环境自带的一些变量:
[root@iZ7xvfomazz4187zib4aurZ test]# printenv
XDG_SESSION_ID=17169
HOSTNAME=iZ7xvfomazz4187zib4aurZ
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=119.133.4.44 3586 22
SSH_TTY=/dev/pts/0
USER=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/var/lib/snapd/snap/bin:/root/bin
PWD=/home/test
LANG=en_US.UTF-8
HISTCONTROL=ignoredups
SHLVL=1
HOME=/root
LOGNAME=root
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
SSH_CONNECTION=119.133.4.44 3586 172.16.195.53 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/0
_=/usr/bin/printenv
OLDPWD=/home
创建变量
用户创建变量语法为 variable=value
,变量名必须遵守下面的规则:
- 字母、数字和下划线字符组成。
- 第一个字符必须是一个字母或一个下划线,不能是数字。
- 不允许出现空格和标点符号。
[root@iZ7xvfomazz4187zib4aurZ test]# b=$(pwd)
读取变量
在变量名前使用 $
就能够读取变量名的值。
[root@iZ7xvfomazz4187zib4aurZ test]# echo $b
/home/test
这里要注意的是如果变量的值也是变量,可以用 ${!varname}
的语法将其展开获得最终的值。
删除变量
unset
命令用来删除一个变量。
[root@iZ7xvfomazz4187zib4aurZ test]# unset b
[root@iZ7xvfomazz4187zib4aurZ test]# echo $b
[root@iZ7xvfomazz4187zib4aurZ test]#
输出变量
export
用来向子 Shell 输出变量。
[root@iZ7xvfomazz4187zib4aurZ ~]# NAME_=Yikuanzz
[root@iZ7xvfomazz4187zib4aurZ ~]# export NAME_
特殊变量
$?
为上一个命令的退出码,用来判断上个命令是否执行成功。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $?
127
$$
为当前 Shell 的进程ID,当然它一般用来命名临时文件比如 LOGFILE=/tmp/output_log.$$
。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $$
30290
[root@iZ7xvfomazz4187zib4aurZ ~]# bash
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $$
30855
$_
为上一个命令的最后一个参数。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $_
/etc/bashrc
$!
为最近一个后台执行的异步命令的进程 ID。
[root@iZ7xvfomazz4187zib4aurZ ~]# bash &
[1] 31071
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $!
31071
[1]+ Stopped bash
$0
为当前 Shell 的名称。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $0
bash
$-
为当前 Shell 的启动参数。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $-
himBH
$@
和 $#
表示脚本的参数数量。
变量默认值
Bash 提供了特殊的语法来保证变量不为空:
${varname:-word}
表示如果变量varname
存在且不为空,则返回它的值,否则返回word
。${varname:=word}
表示如果变量varname
存在且不为空,则返回它的值,否则将它设为word
。${varname:+word}
如果变量名存在且不为空,则返回word
,否则返回空值。${varname:?message}
如果变量varname
存在且不为空,则返回它的值,否则打印出varname: message
,并中断脚本的执行。如果省略了message
,则输出默认的信息“parameter null or not set.”。
上面四种语法如果用在脚本中,变量名的部分可以用到数字 1
到 9
,表示脚本的参数。
比如:filename=${1:?"filename missing."}
。
上面代码出现在脚本中,1
表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。
declare 命令
declare
命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。
它的语法就是:declare OPTION VARIABLE=value
。
declare
命令的主要参数(OPTION)如下。
-a
:声明数组变量。-f
:输出所有函数定义。-F
:输出所有函数名。-i
:声明整数变量。-l
:声明变量为小写字母。-p
:查看变量信息。-r
:声明只读变量,不能unset
。-u
:声明变量为大写字母。-x
:该变量输出为环境变量,等同于export
命令。
declare
命令如果用在函数中,声明的变量只在函数内部有效,等同于 local
命令。
下面是一些例子:
-i
声明变量为整数。
[root@iZ7xvfomazz4187zib4aurZ ~]# declare -i v1=12 v2=3
[root@iZ7xvfomazz4187zib4aurZ ~]# declare -i res
[root@iZ7xvfomazz4187zib4aurZ ~]# res=v1*v2
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $res
36
-u
可以自动将变量转成大写字母。
[root@iZ7xvfomazz4187zib4aurZ ~]# declare -u v3
[root@iZ7xvfomazz4187zib4aurZ ~]# v3=upper
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $v3
UPPER
readonly 命令
readonly
命令等同于 declare -r
,用来声明只读变量,不能改变变量值,也不能 unset
变量。
[root@iZ7xvfomazz4187zib4aurZ ~]# readonly v4=1
[root@iZ7xvfomazz4187zib4aurZ ~]# v4=2
bash: v4: readonly variable
readonly
命令有三个参数。
-f
:声明的变量为函数名。-p
:打印出所有的只读变量。-a
:声明的变量为数组。
let 命令
let
命令声明变量时,可以直接执行算术表达式。
[root@iZ7xvfomazz4187zib4aurZ ~]# let v5=1+2+3
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $v5
6
let
命令还可以对多个变量进行赋值。
[root@iZ7xvfomazz4187zib4aurZ ~]# let "r1=2" "r2 = r1++"
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $r1,$r2
3,2
Bash 函数
函数(function)是可以重复使用的代码片段,有利于代码的复用。
函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。
如果函数与脚本同名,函数会优先执行。但是,函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。
简单定义
函数定义的语法有两种:
# 第一种
fn() {
# codes
}
# 第二种
function fn(){
# codes
}
比如,我们在 Shell 中写下这样一个函数 hello() { echo "Hello $1"}
。
那么我们在命令行中调用它,就会是这样的。
[root@iZ7xvfomazz4187zib4aurZ bin]# hello(){
> echo "Hellp $1"
> }
[root@iZ7xvfomazz4187zib4aurZ bin]# hello world
Hellp world
如果我们想删除一个函数,可以用 unset
命令,它的语法是这样的 unset -f functionName
。
我们还可以用 declare -f
查看已经定义好的所有函数。
参数变量
函数的参数变脸,与脚本参数变量是一致的。
$1
~$9
:函数的第一个到第9个的参数。$0
:函数所在的脚本名。$#
:函数的参数总数。$@
:函数的全部参数,参数之间使用空格分隔。$*
:函数的全部参数,参数之间使用变量$IFS
值的第一个字符分隔,默认为空格,但是可以自定义。
如果函数的参数多于9个,那么第10个参数可以用 ${10}
的形式引用,以此类推。
#!/usr/bin/env bash
function log_msg{
echo "[ `date '+ %F %T'` ]: $@"
}
return 命令
return
命令用于从函数返回一个值,函数执行到这条命令后,就不再继续执行,直接返回。
假设我们有这个函数 function func_return_value { return 10 }
,调用之后我们可以用 $?
拿到返回值 func_return_value; echo "Value returned by function is: $?"
。
全局变量和局部变量
与其他语言有区别的是,Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。
#!/usr/bin/env bash
fn () {
foo=1
echo "fn: foo = $foo"
}
fn
echo "global: foo = $foo"
我们可以在函数里面使用 local
命令来声明局部变量。
#!/usr/bin/env bash
fn () {
local foo
foo=1
echo "fn: foo = $foo"
}
fn
echo "global: foo = $foo"
Bash 数组
数组(array)就是我们所熟悉的那个数组,当然它更像一个列表,元素的下标也是从 0 开始的。
创建数组
我们通过赋值的方法创建数组。
# 逐个赋值
array[0]=val
array[1]=val
array[2]=val
# 一次性赋值
ARRAY=(value1 value2 ... valueN)
此外,我们创建数组的时候可以在每个值前面指定位置。
days=(Sun Mon Tue Wed Thu Fri Sat)
days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)
或者为某些值指定位置,也是可以的。
names=(hatter [5]=duchess alice)
hatter
是数组的 0 号位置,duchess
是 5 号位置,alice
是 6 号位置。
定义数组的时候,可以使用通配符。
mp3s=( *.mp3 )
另外的方式有 declare -a ARRARYNAME
和 read -a ARRARYNAME
。
读取数组
读取数组指定位置的成员,使用 ${array[i]}
。
如果我们想读取所有成员的话,需要用到 @
和 *
的特殊索引,比如 ${array[@]}
。
当我们配合 for
循环语句执行的时候,@
和 *
是有差别的,差别就在于双引号。
#!/usr/bin/env bash
names=( Mike "Glibe robot" Tim "Jame Hardon" susan)
for name in ${names[@]}; do
echo "Name: $name"
done
如果使用 @
并且不使用 ""
的话,它的输出会是这样的。当然,使用 *
的话是一样的。
[root@iZ7xvfomazz4187zib4aurZ bin]# source name.sh
Name: Mike
Name: Glibe
Name: robot
Name: Tim
Name: Jame
Name: Hardon
Name: susan
如果使用 @
并且使用 ""
的话,它的输出会是这样的。
[root@iZ7xvfomazz4187zib4aurZ bin]# source name.sh
Name: Mike
Name: Glibe robot
Name: Tim
Name: Jame Hardon
Name: susan
如果用 *
并且用 ""
的话,它的输出就会是这样的,将所有元素变成一个字符串输出。
[root@iZ7xvfomazz4187zib4aurZ bin]# source name.sh
Name: Mike Glibe robot Tim Jame Hardon susan
数组拷贝
如果我们想拷贝一个数组,我们可以这样写:copyArrary=( "${names[$@]}" )
。
数组长度
我们可以用这两种语法:${#array[*]}
和 ${#array[@]}
。
如果我们用这种语法读取成员的话,就会得到成员字符串的长度,比如我们用 ${#a[100]}
。
成员下标
${!array[@]}
或 ${!array[*]}
,可以返回数组的成员序号,即哪些位置是有值的。
[root@iZ7xvfomazz4187zib4aurZ ~]# arr=([5]=a [9]=b [23]=c)
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${!arr[@]}
5 9 23
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${!arr[*]}
5 9 23
当然,我们也可以通过 for
循环语句来遍历数组。
此外,我们可以通过切片的方式来去提取数组成员,它的语法是 ${array[@]:position:length}
。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
a b c d
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]:1:3}
b c d
追加成员
数组末尾追加成员,可以使用 +=
赋值运算符。它能够自动地把值追加到数组末尾。
[root@iZ7xvfomazz4187zib4aurZ ~]# arr+=(e f g)
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
a b c d e f g
删除数组
用 unset
命令删除一个数组成员,当然删除成员也可以将这个成员设为空值。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
a b c d e f g
[root@iZ7xvfomazz4187zib4aurZ ~]# unset arr[0]
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
b c d e f g
[root@iZ7xvfomazz4187zib4aurZ ~]# arr[1]=''
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
c d e f g
如果我们用 unset ArrayName
命令可以清空整个数组。
[root@iZ7xvfomazz4187zib4aurZ ~]# unset arr
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
关联数组
关联数组使用字符串而不是整数作为数组索引。
[root@iZ7xvfomazz4187zib4aurZ ~]# declare -A colors
[root@iZ7xvfomazz4187zib4aurZ ~]# colors["red"]="#ff0000"
[root@iZ7xvfomazz4187zib4aurZ ~]# colors["green"]="#00ff00"
[root@iZ7xvfomazz4187zib4aurZ ~]# colors["blue"]="#0000ff"
Bash 字符串操作
字符串长度
通过 ${#varname}
查看字符串的长度。
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${#v3}
5
子字符串
通过 ${varname:offset:length}
来提取子串,这个方法只能对变量进行操作。
[root@iZ7xvfomazz4187zib4aurZ ~]# NAME=Yikuanzz
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME:0:6}
Yikuan
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME: -4:4}
anzz
搜索和替换
字符串头部的模式匹配
${variable#pattern}
如果 pattern
匹配变量 variable
的开头,则会删除最短匹配的部分,将剩余部分返回。
${variable##pattern}
如果 pattern
匹配变量 variable
的开头,则会删除最长匹配的部分,将剩余部分返回。
[root@iZ7xvfomazz4187zib4aurZ ~]# myPath=/home/cam/book/long.file.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${myPath#/*/}
cam/book/long.file.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${myPath##/*/}
long.file.name
我们注意到 ${variable#pattern}
模式下 /*/
匹配到的是 /home/
;而 ${variable##pattern}
模式下 /*/
匹配到的是 /home/cam/book/
。
此外,如果要将头部匹配的部分替换成其他内容,可以用 ${variable/#pattern/string}
的语法。
[root@iZ7xvfomazz4187zib4aurZ ~]# FF=JPG.JPG
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${FF/#JPG/jpg}
jpg.JPG
字符串尾部的模式匹配
${variable%pattern}
如果 pattern
匹配变量 variable
的结尾,则会删除最短匹配的部分,将剩余部分返回。
${variable%%pattern}
如果 pattern
匹配变量 variable
的结尾,则会删除最长匹配的部分,将剩余部分返回。
[root@iZ7xvfomazz4187zib4aurZ ~]# path=/home/cam/book/long.file.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path%.*}
/home/cam/book/long.file
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path%%.*}
/home/cam/book/long
此外,如果要将尾部匹配的部分替换成其他内容,可以用 ${variable/%pattern/string}
的语法。
[root@iZ7xvfomazz4187zib4aurZ ~]# FF=JPG.JPG
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${FF/%JPG/jpg}
JPG.jpg
任意位置的模式匹配
${variable/pattern/string}
如果 pattern
匹配变量 variable
的一部分,最长匹配的那部分会被 string
替换,但仅替换第一个匹配的。
${variable//pattern/string}
如果 pattern
匹配变量 variable
的一部分,最长匹配的那部分会被 string
替换,并且所有匹配的部分都会替换。
[root@iZ7xvfomazz4187zib4aurZ ~]# path=/home/cam/foo/foo.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $path
/home/cam/foo/foo.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path/foo/bar}
/home/cam/bar/foo.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path//foo/bar}
/home/cam/bar/bar.name
改变大小写
通过 ${varname^^}
语法可以将变量值转为大写。
通过 ${varname,,}
语法可以将变量值转为小写。
[root@iZ7xvfomazz4187zib4aurZ ~]# NAME=YikuanZz
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME^^}
YIKUANZZ
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME,,}
yikuanzz
Bash 算术运算
算术表达式
在之前我们有提到使用 ((...))
语法可以进行整数运算,这意味着我们可以使用加、减、乘、除、取余、指数等等运算。
此外使用 $((...))
的语法可以在内部用圆括号改变运算顺序,并且可以使用变量,如果变量不存在会被视为零。
[root@iZ7xvfomazz4187zib4aurZ ~]# num=123
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $(((num-23)*10))
1000
数值的进制
Bash 数值默认是十进制,但是在算术表达式中,也可以使用其他进制。
number
:没有任何特殊表示法的数字是十进制数(以10为底)。0number
:八进制数。0xnumber
:十六进制数。base#number
:base
进制的数。
位运算
$((...))
支持以下的二进制位运算符。
<<
:位左移运算,把一个数字的所有位向左移动指定的位。>>
:位右移运算,把一个数字的所有位向右移动指定的位。&
:位的“与”运算,对两个数字的所有位执行一个AND
操作。|
:位的“或”运算,对两个数字的所有位执行一个OR
操作。~
:位的“否”运算,对一个数字的所有位取反。!
:逻辑“否”运算^
:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。
逻辑运算
$((...))
支持以下的逻辑运算符。
<
:小于>
:大于<=
:小于或相等>=
:大于或相等==
:相等!=
:不相等&&
:逻辑与||
:逻辑或expr1?expr2:expr3
:三元条件运算符。若表达式expr1
的计算结果为非零值(算术真),则执行表达式expr2
,否则执行表达式expr3
。
expr 命令
expr 命令支持算术运算和变量替换,但是不支持非整数运算。
Bash 脚本入门
脚本(script)就是包含一系列命令的一个文本文件。Shell 读取这个文件,依次执行里面的所有命令,就好像这些命令直接输入到命令行一样。
Shebang 行
脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!
字符开头,这个字符称为 Shebang,所以这一行就叫做 Shebang 行。
#!
后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh
或/bin/bash
。
如果 Bash 解释器不放在目录/bin
,脚本就无法执行了。为了保险,可以写成这样:#!/usr/bin/env bash
。
如果没有 Shebang 行,就可能要手动将脚本传给解释器来执行。
env 命令 总是指向
/usr/bin/env
文件,或者说这个二进制文件总是在目录/usr/bin
中。
#!/usr/bin/env NAME
这个语法的意思是,让 Shell 查找$PATH
环境变量里面第一个匹配的NAME
。如果你不知道某个命令的具体路径,或者希望兼容其他用户的机器,这样的写法就很有用。
脚本路径
我们除了要给脚本权限外,还要指定脚本的路径。
如果我们将脚本放在环境变量 $PATH
指定的目录中,就不需要指定路径了。
建议在主目录新建一个~/bin
子目录,专门存放可执行脚本,然后把~/bin
加入到$PATH
的末尾,
[root@iZ7xvfomazz4187zib4aurZ ~]# export PATH=$PATH:~/bin
[root@iZ7xvfomazz4187zib4aurZ ~]# source ~/.bashrc
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/var/lib/snapd/snap/bin:/root/bin:/root/bin
脚本参数
在调用脚本的时候,文件名后可以带有参数,比如 script.sh word1 word2 word3
。
脚本文件内部,可以用特殊变量引用这些参数:
$0
:脚本文件名,即script.sh
。$1
~$9
:对应脚本的第一个参数到第九个参数。$#
:参数的总数。$@
:全部的参数,参数之间使用空格分隔。$*
:全部的参数,参数之间使用变量$IFS
值的第一个字符分隔,默认为空格,但是可以自定义。
如果脚本的参数多于9个,那么第10个参数可以用 ${10}
的形式引用,以此类推。
shift 命令 可以改变脚本参数,每次执行都会移除脚本当前的第一个参数,让后面的参数向前,以此类推。此外也可以移除多个参数,比如
shift 3
就可以移除前三个参数。#!/usr/bin/env bash echo "一共输入了 &# 个参数" while ["$1" != ""]; do echo "剩下 $# 个参数" echo "参数:$1" shift done
getopts 命令 用在脚本内部,可以解析复杂的脚本命令行参数,取出脚本所有带有前置连词线
-
的参数。语法为
getopts optstring name
其中第一参数为所有连词线参数,第二个参数为变量名用于储存当前取到的配置项参数。假设,某个脚本参数有
-l
、-h
、-a
,其中-a
是可以带参数的,那么optstring
可以写为lha:
。while getopts 'lha:' OPTION; do case "$OPTION" in l) echo "linuxconfig" ;; h) echo "h stands for h" ;; a) avalue="$OPTARG" echo "The value provided is $OPTARG" ;; ?) echo "script usage: $(basement $0)[-l][-h][-a somevalue]" >& 2 # 退出值为 0 表示正常;1 表示错误;2 表示用法不对 exit 1 ;; esac done # $OPTIND 在 getopts 前为 1 每次执行都会加 1 shift "$(($OPTIND - 1))"
--
参数终止符 可以让指定变量作为实体参数,而不是配置项参数。比如,
myPath="~/docs" ls --$myPath
就会将其作为实体参数解释。
命令执行结果
命令执行结束后,会有一个返回值。0
表示执行成功,非 0
(通常是 1
)表示执行失败。环境变量 $?
可以读取前一个命令的返回值。
#!/usr/bin/env bash
cd $some_directory
if ["$?" = "0"]; then
rm *
else
echo "无法切换目录!" 1>&2
exit 1
fi
更简洁的写法是:
cd $some_directory && rm *
cd $some_directory || exit 1
source 命令
source
命令用于执行一个脚本,通常用于重新加载一个配置文件。
比如,source .bashrc
。
它会在当前 Shell 中执行脚本,如果用 bash
则会新建一个 子Shell 来执行。
因此,用 source
执行脚本的话,就可以用当前 Shell 的变量;如果用 bash
的话,就需要将变量 export
到 子Shell 才能使用。
此外,source
命令的另一个用途是在脚本内部加载外部库。
比如,source ./lib.sh
或者 ..bashrc
。
alias 命令
alias
命令用来为一个命令指定别名,它的语法是 alias NAME=DEFINITION
。
比如,alias search=grep
。
我们可以通过 alias
命令查看所有别名。
通过 unalias
命令解除别名。