陈斌彬的技术博客

Stay foolish,stay hungry

Shell 常用命令大全

第一招 HelloWorld

第一式:echo

echo "Hello World"
echo -n "Hello World"    # 不带换行
echo -e '\e[0;33;1mHello\e[0m World'   # 带颜色的玩法
echo -e '\e[0;33;4mHello\e[0m World'   # 带颜色+下划线
echo -e '\e[0;33;5mHello\e[0m World'   # 带颜色+闪烁

格式为 \e[背景色;前景色;高亮格式m

第二招 判断

第一式:if

if true
then
    echo "Hello World"
else
    echo "Bug"
fi

if false
then
    echo "Hello World"
elif true
then
    echo "Bug"
else
    echo "Bee"
fi

判断原理

if、elif 会执行它后面跟着的命令,然后看返回值是否为 0,如果为 0 则执行 then 下面的语句块,否则执行 else 下面的语句块。

apple@ubuntu:~$ true
apple@ubuntu:~$ echo $?
0
apple@ubuntu:~$ false
apple@ubuntu:~$ echo $?
1

注:

true、false 事实上也为一个命令,true 的返回码必为 0,false 的返回码必为 1

$? 为 shell 内置变量,用于存放上一个命令的返回码

第二式:test、[ ] 和 [[ ]]

test[ ][[ ]] 实际上都是 shell 中的命令,执行之后会返回 10,而这几个命令与 if 相结合可以达到我们所需要的许多判断功能,例如测试字符串是否为空的三种写法:

s=""
if [ -z ${s} ]
then
    echo "empty"
fi

if [[ -z ${s} ]]
then
    echo "empty"
fi

if test -z ${s}
then
    echo "empty"
fi

事实上,if 后的 [ ]、[[ ]]、test 命令都是可以单独执行的,而根据 if 的判断原理,后续执行哪个分支也是由 [ ]、[[ ]]、test 的返回值来决定的,以下是单独执行它们的效果:

apple@ubuntu:~$ s=""
apple@ubuntu:~$ [ -z "${s}"]
apple@ubuntu:~$ echo $?
0
apple@ubuntu:~$ s="abc"
apple@ubuntu:~$ test -z "${s}"
apple@ubuntu:~$ echo $?
1
apple@ubuntu:~$ s="123"
apple@ubuntu:~$ [[ 100 -lt ${s} ]]
apple@ubuntu:~$ echo $?
0

在性能方面 [ ]test 性能基本相同,[[ ]] 性能是最高的,为前两者的5倍左右(以 -d 运算符测试),所以建议尽量使用 [[ ]] 提高脚本性能。

文件测试

运算符   描述  示例
-e filename 如果 filename 存在,则为真  [ -e /var/log/syslog ]
-d filename 如果 filename 为目录,则为真   [ -d /tmp/mydir ]
-f filename 如果 filename 为常规文件,则为真 [ -f /usr/bin/grep ]
-L filename 如果 filename 为符号链接,则为真 [ -L /usr/bin/grep ]
-r filename 如果 filename 可读,则为真  [ -r /var/log/syslog ]
-w filename 如果 filename 可写,则为真  [ -w /var/mytmp.txt ]
-x filename 如果 filename 可执行,则为真   [ -L /usr/bin/grep ]
filename1 -nt filename2 如果 filename1 比 filename2 新,则为真  [ /tmp/install/etc/services -nt /etc/services ]
filename1 -ot filename2 如果 filename1 比 filename2 旧,则为真  [ /boot/bzImage -ot arch/i386/boot/bzImage ]

字符串比较

运算符   描述  示例
-z string   如果 string 长度为零,则为真  [ -z "${myvar}" ]
-n string   如果 string 长度非零,则为真  [ -n "${myvar}" ]
string1 = string2   如果 string1 与 string2 相同,则为真   [ "${myvar}" = "abc" ]
string1 != string2  如果 string1 与 string2 不同,则为真   [ "${myvar}" != "abc" ]
string1 < string    如果 string1 小于 string2,则为真   [ "${myvar}" \< "abc" ]
[[ "${myvar}" < "abc" ]]
string1 > string    如果 string1 大于 string2,则为真   [ "${myvar}" \> "abc" ]
[[ "${myvar}" > "abc" ]]

注意:

在字符串两边加上 ”“ 防止出错

<> 是字符串比较,不要错用成整数比较

如果是在 [ ] 中使用 <>,需要将它们写成 \<\>

整数比较

运算符   描述  示例
num1 -eq num2   等于  [ 3 -eq $mynum ]
num1 -ne num2   不等于   [ 3 -ne $mynum ]
num1 -lt num2   小于  [ 3 -lt $mynum ]
num1 -le num2   小于或等于 [ 3 -le $mynum ]
num1 -ge num2   大于或等于 [ 3 -ge $mynum ]

第三式:&&、||

&& 可以用来对两个判断语句求与

if [ -n "abc" ] && [ -n "aa" ]
if [[ -n "abc" ]] && [[ -n "aa" ]]
if test -n "abc" && test -n "aa"
if [[ -n "abc" && -n "aa" ]]

注:只有 [[ ]] 才允许把 && 写在里面

|| 可以用来对两个判断语句求或

if [ -n "abc" ] || [ -n "aa" ]
if [[ -n "abc" ]] || [[ -n "aa" ]]
if test -n "abc" || test -n "aa"
if [[ -n "abc" || -n "aa" ]]

小技巧

&&、||还可以用来拼接命令,达到按前一个命令成功与否来决定是否执行后一个命令的效果

cd /data && ls         # 当`cd /data`返回0(即成功)时才执行后面的`ls`
cd /data || cd /root   # 当`cd /data`返回非0(即失败)时才执行后面的`cd /root`

第三招:循环

第一式:for

for i in {1..100}
do
    echo ${i}
done

注:

{1..100} 属于通配的一种写法,展开会是 1 2 3 ... 100(1~100以空格隔开) 这样的字串。

例如 for i in 1 2 3;这样的语句,for 会将 1、2、3 依次赋值于 i 进行循环,而对于通配的情况,for 则会将通配展开后将里面的每一项依次赋值于 i 进行循环。

for i in `seq 100`
do
    echo ${i}
done

for i in `seq 1 2 100`
do
    echo ${i}
done

注:

seq 本身为一个命令,用于输出数字组成的序列,如 seq 100 会生成并输出 1 2 3 ... 100(1~100以换行符隔开)这样的序列,而 seq 1 2 100 则会生成并输出 1 3 5 ... 99(以1开始,2为公差的等差数列中小于 100 的项,以换行符隔开)。 反引号 (`) 之间的命令会被执行,其输出结果会转换成一个变量,故上面的 for in 会依次取出 seq 的执行结果赋值于 i 进行循环。

for ((i = 0; i < 100; i++))
do
    echo ${i}
done

for ((i = 0; i < 100; i+= 2))
do
    echo ${i}
done

注:

以上与 C 语言式的 for 循环语法基本相同,区别在于双重括号:(( ))

第二式:while、until

i=0
while [[ ${i} -lt 100 ]]
do
    echo ${i}
    ((i++))
done

i=0
until [[ ${i} -ge 100 ]]
do
    echo ${i}
    ((i++))
done

注:

while 和 until 的判断原理与 if 是类似的,它会执行并它后面跟着的命令,不同点在于:

while 是后面语句返回值为 0,则执行循环中的语句块,否则跳出循环;

until 则是后面语句返回值 非0,则执行循环中的语句块,否则跳出循环。

第四招:变量

第一式:整数

整数的运算

方法较多,此处只列举最浅显易懂,并且效率最高的办法——直接将符合C语言语法的表达式放到 (( ))中即可达到对整数的计算目的:

echo $(( 1+1 ))        # 最简单的1+1
echo $(( (1+2)*3/4 ))  # 表达式中还可以带括号
echo $(( 1<<32 ))      # 左移右移也支持,但仅限于-4294967296~4294967296之间的数值
echo $(( 1&3 ))        # &、^、|、~ 这样的位操作亦支持
(( i=1+2 ))            # 将1+2计算出结果后赋值给i,后续若`echo ${i}`会得到3
(( i++ ))              # 变量i自增1
(( i+=3 ))             # 变量i自增3
## ...                  # 还有很多,不再详列

注:

进行整数运算的方法还有:expr$[]letshell 等内置命令,也可调用 bc、python 等外部工具进行更复杂的数学运算

第二式:字符串

替换

操作                  含义

${string/old/new}   string中第一个old替换为new
${string//old/new}  string中所有old替换为new

apple@ubuntu:~$ s="I hate hate you"
apple@ubuntu:~$ echo ${s/hate/love}
I love hate you
apple@ubuntu:~$ echo ${s//hate/love}
I love love you

截取子串

操作             含义
${string:n}     string从下标n到结尾的子串
${string:n:m}   string从下标n到m的子串
${string::m}    string从下标0到m的子串

apple@ubuntu:~$ s="0123456789"
apple@ubuntu:~$ echo ${s:3}
3456789
apple@ubuntu:~$ echo ${s::3}
012
apple@ubuntu:~$ echo ${s:0:3}
012
apple@ubuntu:~$ echo ${s:2:5}
23456

通配删除

通配删除,即按通配符,删除掉字符串中符合条件的一部分

操作 含义

${string:#pattern}  string从左到右删除pattern的最小通配
${string:##pattern} string从左到右删除pattern的最大通配
${string:%pattern}  string从右到左删除pattern的最小通配
${string:%%pattern} string从右到左删除pattern的最大通配

注:

最小通配和最大通配的区别:

  • 最小通配:符合通配的最小子串
  • 最大通配:符合通配的最大子串

例如 string 值为 /00/01/02/dir,对于通配 /*/,其最小通配为 /00/,而最大通配 /00/01/02/

apple@ubuntu:~$ s="/00/01/02/dir"
apple@ubuntu:~$ echo ${s#/*/}
01/02/dir
apple@ubuntu:~$ echo ${s##/*/}
dir
apple@ubuntu:~$ s="abc/cde/efg"
apple@ubuntu:~$ echo ${s%/*}
abc/cde
apple@ubuntu:~$ echo ${s%%/*}
abc

小技巧

1.获取文件名:${path##*/} (相当于 basename 命令的功能)

2.获取目录名:${path%/*} (相当于 dirname 命令的功能)

3.获取后缀名:${path##*.}

apple@ubuntu:~$ s="/root/tesst/dir/sudir/abc.txt"
apple@ubuntu:~$ echo ${s##*/}
abc.txt
apple@ubuntu:~$ echo ${s%/*}
/root/tesst/dir/sudir
apple@ubuntu:~$ echo ${s##*.}
txt

第三式:数组

普通数组

a=()         # 空数组
a=(1 2 3)    # 元素为1,2,3的数组
echo ${#a[*]}  # 数组长度
echo ${a[2]}   # 下标为2的元素值(下标从0开始)
a[1]=0         # 给下标为1的元素赋值

# 遍历数组
for i in ${a[*]}
do
    echo ${i}
done

unset a        # 清空数组

关联数组

关联数组可以用于存储 key-value 型的数据,其功能相当于 C++ 中的 mappython 中的 dict

declare -A a        # 声明关联数组(必须有此句)
a=(["apple"]="a1" ["banana"]="b2" ["carrot"]="c3")   # 初始化关联数组
echo ${#a[*]}       # 获取元素个数
echo ${a["carrot"]} # 获取元素值  
a["durian"]="d4"    # 插入或修改元素
echo ${!a[*]}       # 获取所有的key
unset a["banana"]   # 删除元素

# 遍历数组(仅value)
for i in ${a[*]}
do
    echo ${i}
done

# 遍历数组(key和value)
for key in ${!a[*]}
do
    echo "${key} ==> ${a[${key}]}"
done

unset a             # 清空数组

注:

关联数组需要 bash 4.0 以上版本才支持,选用需慎重。查看 bash 版本用 bash --version 关联数组必须用 declare -A��� 声明类型,否则数值会出错。

第四式:将命令执行结果存入变量

` ` 与 $( )

LINE_CNT=`wc -l test.txt`

LINE_CNT=$(wc -l test.txt)

以上命令均可把 wc -l test.txt 的结果存入 LINE_CNT 变量中

注: ` ` 和 $( ) 都只将命令行标准输出的内容存入变量,如果需要将标准错误内容存入变量,需要用到重定向。

换行符处理

如果命令执行结果有多行内容,存入变量并打印时换行符会丢失:

apple@ubuntu:~$ cat test.txt
a
b
c

apple@ubuntu:~$ CONTENT=`cat test.txt`
apple@ubuntu:~$ echo ${CONTENT}
a b c
apple@ubuntu:~$ CONTENT=`cat test.txt`
apple@ubuntu:~$ echo "${CONTENT}"
a
b
c

第五招:重定向

标准输入流、标准输出流、标准错误流

名称          英文缩写      内容                      默认绑定位置                  文件路径          Shell中代号
标准输入流   stdin       程序读取的用户输入             键盘输入                    /dev/stdin         0
标准输出流   stdout      程序的打印的正常信息         终端(terminal), 即显示器     /dev/stdin         1
标准错误流   stderr      程序的错误信息            终端(terminal),, 即显示器   /dev/stderr       2

重定向方式一览表

操作  含义
cmd > file                     把 stdout 重定向到 file
cmd >> file                    把 stdout 追加到 file
cmd 2> file                    把 stderr 重定向到 file
cmd 2>> file                   把 stderr 追加到 file
cmd &> file                    把 stdout 和 stderr 重定向到 file
cmd > file 2>&1                把 stdout 和 stderr 重定向到 file
cmd >> file 2>&1               把 stdout 和 stderr 追加到 file
cmd file2 cmd                  cmd 以 file 作为 stdin,以 file2 作为 stdout
cat <>file                     以读写的方式打开 file
cmd < file cmd                 cmd 命令以 file 文件作为 stdin
cmd << delimiter Here document 从 stdin 中读入,直至遇到 delimiter 分界符

第一式:重定向标准输出流(stdout)

把程序打印的内容输出到文件

# 以下两种方式都会将`Hello World`写入到hello.txt(若不存在则创建)
echo "Hello World" > hello.txt   # hello.txt原有的将被覆盖
echo "Hello World" >> hello.txt  # hello.txt原有内容后追加`Hello World`

第二式:重定向标准错误流(stderr)

把程序的错误信息输出到文件

例如文件路径中不存在 +++ 这个文件:

apple@ubuntu:~$ ls +++
ls: cannot access +++: No such file or directory
apple@ubuntu:~$ ls +++ > out.txt
ls: cannot access +++: No such file or directory

上面的 ls +++ 后输出的内容为标准错误流中的错误信息,所以即使用 > out.txt 重定向标准输入,错误信息仍然被打印到了屏幕。

# 以下两种方式都会将`ls +++`输出的错误信息输出到err.txt(若不存在则创建)
ls +++ 2> err.txt    # err.txt原有内容将被覆盖
ls +++ 2>> err.txt   # err.txt原有内容后追加内容

第三式:重定向标准输入流(stdin)

1.让程序从文件读取输入

以默认从标准输入读取表达式,并进行数学计算的命令 bc 为例:

apple@ubuntu:~$ bc -q
1+1
2

注:

  • 1+1 为键盘输入的内容,2bc 命令打印的计算结果
  • bc 后的 -q参数用于禁止输出欢迎信息
  • 以上重定向方法格式为命令 < 文件路径

如果我需要把已经存在文件 exp.txt 中的一个表达式输入到 bc 中进行计算,可以采用重定向标准输入流的方法:

bc -q < exp.txt

注:

exp.txt 中内容为 1+1 时,以上语句输出 2 由于 bc 命令本身支持从文件输入,如不使用重定向,也可用 bc exp.txt 达到相同效果

apple@ubuntu:~$ cat > exp.txt
1+1
apple@ubuntu:~$ bc -q < exp.txt
2
2.将变量中内容作为程序输入
EXP="1+1"
bc -q <<< "${EXP}"

注:

1.以上代码等同于执行 bc 并输入 1+1,得到的输出为 2

2.以上重定向方法格式为命令 <<< 变量内容

3.将当前 shell 脚本中的多行内容作为程序的输入
apple@ubuntu:~$ bc << EOF
> 1+1
> 2+2
> EOF
2
4
apple@ubuntu:~$ EXP="1+1"
apple@ubuntu:~$ VAR="2"
apple@ubuntu:~$ bc << EOF
> ${EXP}
> 2+${VAR}
> EOF
2
4

注:

1.以上用法可以方便地将某个程序需要的多行输入内容直接包含在当前脚本中

2.支持变量,可以动态地改变多行输入的内容

3.以上重定向方法格式为:命令 << EOF (换行)...(换行) EOF,其中的 EOF 换成其它字符串也是有效的,如:命令 << ABC (换行)...(换行) ABC 的,但通用习惯都使用 EOF