• 快速上手Shell,看这一篇就够了


    一、Shell简介

    什么是Shell?

    • Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。
    • Shell 既是一种命令语言,又是一种程序设计语言。
    • Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务。

    什么是Shell脚本?

    • Shell 脚本(Shell Script),是一种为 Shell 编写的脚本程序,一般文件后缀为 .sh
    • 业界所说的 Shell 通常都是指 Shell 脚本,但 Shell 和 Shell Script 是两个不同的概念。

    Shell 编程跟 Java、Python 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。我们通常用 Vim 来编写 Shell 脚本,至于 Shell 解释器,其种类众多,常见的有

    • sh:即 Bourne Shell,sh 是 Unix 标准默认的 shell。
    • bash:即 Bourne Again Shell,bash 是 Linux 标准默认的 shell。
    • zsh:功能强大的 shell 与脚本语言。

    我们可以使用如下命令来查看当前使用的 Shell

    echo $SHELL
    
    • 1

    若要查看所有的 Shell,则可执行

    cat /etc/shells
    
    • 1

    如果要切换 Shell,例如切换到 bash,则可执行如下命令然后重启终端

    chsh -s /bin/bash
    
    • 1

    ⚠️ 由于 bash 是 Linux 标准默认的 Shell 解释器,所以本文接下来将主要关注 bash 的使用。

    二、基础部分

    2.1 Hello World

    在 shell 脚本中,#! 后跟的路径用来告诉系统此脚本应用什么解释器来执行,具体请见 Shebang

    接下来我们创建一个脚本文件

    cd && touch demo.sh && vim demo.sh
    
    • 1

    向其中写入内容

    #!/bin/bash
    echo "Hello World"
    
    • 1
    • 2

    之后保存并退出,然后执行该脚本

    chmod u+x demo.sh && ./demo.sh
    
    • 1

    需要注意,创建的 demo.sh 默认是没有可执行权限的,所以我们需要为其添加可执行权限。此外,执行命令不可以直接写成 demo.sh,否则系统会去 PATH 里寻找有没有叫 demo.sh 的命令。执行 ./demo.sh 后,系统会自动使用第一行所提到的 bash 解释器来执行该脚本。

    📝 使用 #!/usr/bin/env 脚本解释器名称 是一种常见的在不同平台上都能正确找到解释器的办法,因为系统会自动在 PATH 环境变量中查找你指定的程序。

    当然我们也不必在第一行指定解释器,更简洁地执行脚本的做法是(以 bash 为例)

    bash demo.sh
    
    • 1

    此时即使在第一行指定了解释器也没用。

    2.2 模式

    shell 有交互模式非交互模式两种。

    shell 的交互模式可以简单理解为在命令行中执行命令,如下

    user@host:~$
    
    • 1

    此时我们可以输入一系列 Linux 指令,例如 lscd 等。

    shell 的非交互模式可以简单理解为执行 shell 脚本,例如 2.1 节中提到的例子。

    2.3 注释

    shell 的注释分为单行注释和多行注释,其中单行注释和python相同,都以 # 开头,例如

    # This is a comment.
    echo "Hello World"
    
    • 1
    • 2

    shell 的多行注释以 :< 开头,以 EOF 结束,例如

    :<<EOF
    注释内容...
    注释内容...
    注释内容...
    EOF
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.4 变量

    跟许多程序设计语言一样,我们可以在 bash 中创建变量。bash 中没有数据类型,bash 中的变量可以保存一个数字、一个字符、一个字符串等等,同时无需提前声明变量,给变量赋值会直接创建变量。

    变量命名原则:

    • 命名只能用英文字母,数字和下划线,且开头不能是数字。
    • 不能包含 bash 中的关键字。

    可以看出 bash 中的变量和 python 中的变量十分相似,但需要注意的是,在对变量进行赋值时,等号两边不能出现空格

    var = 'apple'  # 错误
    var='apple'  # 正确
    
    • 1
    • 2

    在定义了变量后,又该如何去使用他们呢?例如如何用 echo 命令输出变量?

    使用一个定义过的变量,只要在变量名前面加 $ 即可:${var}$var。其中花括号加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。

    例如

    fruit='apple'
    # 以下两者等价
    echo ${fruit}
    echo $fruit
    
    • 1
    • 2
    • 3
    • 4

    有时候我们可能需要将变量和其他字符串一起输出,此时应用双引号(不能是单引号,后续会讲)将他们括住:

    fruit='apple'
    # 第一条语句输出变量fruit和字符串abc,最终结果是appleabc
    echo "${fruit}abc"
    # 第二条语句输出变量fruitabc,但因为没有这个变量所以不会输出任何东西(该变量的值为空)
    echo "$fruitabc"
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们可以像 python 那样对已定义的变量重新赋值

    fruit='apple'
    fruit=123
    echo ${fruit}
    # 输出:123
    
    • 1
    • 2
    • 3
    • 4

    2.4.1 只读变量

    如果不希望定义的变量后续被修改,可以使用 readonly

    var=123
    readonly var
    var='abc'
    
    • 1
    • 2
    • 3

    此时会报错

    demo.sh: line 3: var: readonly variable
    
    • 1

    2.4.2 删除变量

    删除一个变量可以使用 unset 命令

    var=123
    unset var
    echo ${var}
    
    • 1
    • 2
    • 3

    因为删除了变量所以最终输出结果为空。

    2.4.3 变量类型

    • 局部变量:局部变量是仅在某个脚本内部有效的变量,它们不能被其他的程序和脚本访问。
    • 环境变量:环境变量是对当前 shell 会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是 export 关键字,shell 脚本也可以定义环境变量。

    常见的环境变量:

    变量描述
    $HOME当前用户的家目录
    $PWD当前工作目录
    $PATH用分号分隔的目录列表,shell 会到这些目录中查找命令
    $SHELL本机可以使用的 shell
    $RANDOM0 - 32767之间的随机整数
    $UID当前用户的用户ID

    如需了解更多,请参考 Bash Guide for Beginners

    2.5 字符串输出

    2.5.1 echo

    echo 命令用来输出字符串。

    输出字符串

    # 以下两者等价
    echo "Hello World"
    echo Hello World
    
    • 1
    • 2
    • 3

    输出转义字符

    sentence="\"This is a sentence\""
    echo ${sentence}
    # 输出结果:"This is a sentence"
    
    • 1
    • 2
    • 3

    输出变量

    read name  # 从键盘中读取一行,并将其赋值给name变量
    echo "How are you, ${name}?"
    
    • 1
    • 2

    输出换行

    echo -e "YES\nNO"  # -e 代表开启转义
    # 输出:
    # YES
    # NO
    
    • 1
    • 2
    • 3
    • 4

    输出不换行

    echo -e "YES\c"
    echo "NO"
    # YESNO
    
    • 1
    • 2
    • 3

    输出命令执行结果(注意不是单引号而是反引号)

    echo `pwd`
    
    • 1

    将输出重定向至文件

    echo "Hello" > test.txt
    
    • 1

    2.5.2 printf

    printf 命令模仿的是 C 语言里的 printf() 语句。默认情况下,printf 不会像 echo 一样自动添加换行符,如需换行可以手动添加 \n

    以下两条语句等价

    echo "Hello World"
    printf "Hello World\n"
    
    • 1
    • 2

    printf 的语法为

    printf format-string [arguments...]
    
    • 1

    其中 format-string 为格式控制字符串,arguments 为参数列表。

    与 C 语言类似,format-string 中的 %s 代表字符串,%c 代表字符,%d 代表整数,%f 代表浮点数。%-10s 表示一个宽度为10的字符串,其中前面的 - 表示左对齐,如果没有代表右对齐。

    printf "%d %s\n" 1 "abc"
    # 输出:1 abc
    
    # 单引号也可以
    printf '%d %s\n' 1 "abc"
    # 输出:1 abc
    
    # 没有引号也可以输出
    printf %s abcdef
    # 输出:abcdef
    
    # 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用
    printf %s abc def
    # 输出:abcdef
    
    printf "%s %s %s\n" a b c d e f g h i j
    # 输出:
    # a b c
    # d e f
    # g h i
    # j
    
    # 如果没有参数,那么 %s 用 NULL 代替,%d 用 0 代替
    printf "%s and %d \n"
    # 输出: and 0
    
    • 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

    2.6 字符串

    shell 中的字符串可以用单引号,也可以用双引号,也可以不用引号。

    单引号字符串的限制

    • 单引号里的任何字符都会原样输出,即单引号里无法识别变量;
    • 单引号里不能出现单独的一个单引号(转义也不行),但可成对出现,作为字符串拼接使用。

    比起单引号,双引号可以识别变量,并且支持转义字符,因此推荐使用双引号。

    2.6.1 获取字符串长度

    string="abcde"
    echo ${#string}
    # 5
    
    • 1
    • 2
    • 3

    2.6.2 字符串切片

    从第二个字符开始,往后截取三个字符

    echo ${string:1:3}
    # bcd
    
    • 1
    • 2

    2.7 数组

    bash 只支持一维数组(不支持多维数组),数组中可以存放多个不同类型的值,初始化时不需要定义数组大小。数组下标从 0 开始,下标可以是整数或算术表达式,其值应大于或等于 0。

    数组用括号来表示,元素用空格分隔开,语法格式如下

    array=(value1 value2 ... valuen)
    
    • 1

    2.7.1 创建数组

    我们可以用上面提到的格式来创建一个数组

    array=(1 "A" 3.2)
    
    • 1

    我们也可以采用赋值的方法来创建一个数组

    array[0]=1
    array[1]="A"
    array[2]=3.2
    
    • 1
    • 2
    • 3

    2.7.2 访问数组

    访问数组中的单个元素的格式为:${array[index]},例如

    nums=(3 2 1)
    echo ${nums[2]}
    # 1
    
    • 1
    • 2
    • 3

    若要访问数组的所有元素,则可将 index 改为 *@,具体如下

    array=(A B C D)
    echo ${array[*]}
    # A B C D
    echo ${array[@]}
    # A B C D
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们同样可以像对待字符串那样对数组进行切片

    nums=(3 4 6 1 9 2)
    echo ${nums[*]:2:3}
    # 6 1 9
    
    • 1
    • 2
    • 3

    2.7.3 获取数组长度

    nums=(3 4 6 1 9 2)
    echo ${#nums[*]}
    # 6
    
    • 1
    • 2
    • 3

    2.7.4 添加/删除元素

    添加元素(在首、尾处添加1,5)

    nums=(2 3 4)
    nums=(1 ${nums[*]} 5)
    echo ${nums[*]}
    # 1 2 3 4 5
    
    • 1
    • 2
    • 3
    • 4

    删除元素

    nums=(2 3 4)
    unset nums[1]
    echo ${nums[*]}
    # 2 4
    
    • 1
    • 2
    • 3
    • 4

    三、进阶部分

    3.1 运算符

    shell 和其他编程语言一样,支持多种运算符,这里仅列出常用的几种:

    • 算术运算符
    • 关系运算符
    • 布尔运算符
    • 字符串运算符

    3.1.1 算术运算符

    原生 bash 不支持简单的数学运算,但是可以通过其他命令来实现,例如 expr,它是一款表达式计算工具,使用它能完成表达式的求值操作。

    一个简单的例子:

    val=`expr 2 + 3`
    echo "两数之和为: ${val}"
    
    • 1
    • 2

    有两点需要注意

    • 表达式和运算符之间要有空格,例如 2+32+ 32 +3 都是不对的,必须写成 2 + 3
    • 完整的表达式要被反引号 `` 包含(注意不是单引号)。

    一些例子

    a=10
    b=3
    
    echo `expr $a + $b`
    # 13
    
    echo `expr $a - $b`
    # 7
    
    echo `expr $a \* $b`  # 注意*需要转义
    # 30
    
    echo `expr $a / $b`
    # 3
    
    echo `expr $a % $b`
    # 1
    
    if [ $a == $b ]
    then
    	echo "a等于b"
    else
    	echo "a不等于b"
    fi
    # a不等于b
    
    # 将b的值赋给a
    a=$b
    if [ $a != $b ]
    then
    	echo "a不等于b"
    else
    	echo "a等于b"
    fi
    # a等于b
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35

    最后两个例子涉及到了条件表达式,条件表达式要放在方括号之间,并且要有空格。例如 [ $a == $b][$a == $b ][$a == $b] 都是不对的,必须写成 [ $a == $b ]

    条件表达式通常与 if 等结合,如果直接用 echo 输出不会得到像 python 那样的 TrueFalse 的结果

    echo [ $a == $b ]
    # [ 10 == 3 ]
    
    • 1
    • 2

    3.1.2 关系运算符

    关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

    这里仅介绍六个最常用的关系运算符。

    关系运算符描述
    -eq即 equal,用来判断两个数是否相等
    -ne即 not equal,用来判断两个数是否不等
    -gt即 greater than,用来判断左边的数是否大于右边的
    -lt即 less than,用来判断左边的数是否小于右边的
    -ge即 greater equal,用来判断左边的数是否大于等于右边的
    -le即 less equal,用来判断左边的数是否小于等于右边的

    这里以 -ge 为例,其他可类比

    a=5
    b=6
    
    if [ $a -ge $b ]
    then
    	echo "$a >= $b"
    else
    	echo "$a < $b"
    fi
    # 5 < 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.1.3 布尔运算符

    布尔运算符描述
    !非运算
    -o或运算
    -a与运算

    一些例子

    a=5
    b=3
    
    if [ ! $a -ge $b ]
    then
    	echo "$a < $b"
    else
    	echo "$a >= $b"
    fi
    # 5 >= 3
    
    if [ $a -gt 3 -a $b -lt 4 ]
    then
    	echo "$a > 3 且 $b < 4 为真"
    else
    	echo "$a > 3 且 $b < 4 为假"
    fi
    # 5 > 3 且 3 < 4 为真
    
    if [ $a -gt 6 -o $b -lt 2 ]
    then
    	echo "$a > 6 或 $b < 2 为真"
    else
    	echo "$a > 6 或 $b < 2 为假"
    fi
    # 5 > 6 或 3 < 2 为假
    
    • 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

    3.1.4 字符串运算符

    字符串运算符描述
    ==检查两个字符串是否相等
    !=检查两个字符串是否不等
    -z检查字符串长度是否为0
    -n检查字符串长度是否不为0
    $检查字符串是否不为空

    一些例子

    a="abc"
    b=""
    
    if [ -z $a ]
    then
    	echo "字符串a的长度为0"
    else
    	echo "字符串a的长度不为0"
    fi
    # 字符串a的长度不为0
    
    if [ $b ]
    then
    	echo "字符串b不为空"
    else
    	echo "字符串b为空"
    fi
    # 字符串b为空
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.2 条件语句

    3.2.1 if

    if 的语法格式

    if condition; then
        # Command...
    fi
    
    • 1
    • 2
    • 3

    if-else 的语法格式

    if condition; then
        # Command...
    else
        # Command...
    fi
    
    • 1
    • 2
    • 3
    • 4
    • 5

    if-elif-else 的语法格式

    if condition1; then
        # Command...
    elif condition2; then 
        # Command...
    else
        # Command...
    fi
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    例如

    a=15
    b=13
    
    if [ $a == $b ]; then
    	echo "a等于b"
    elif [ $a -gt $b ]; then
    	echo "a大于b"
    elif [ $a -lt $b ]; then
    	echo "a小于b"
    else
    	echo "???"
    fi
    # a大于b
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.2.2 case

    如果你需要面对很多情况,分别要采取不同的措施,那么使用 case 会比嵌套的 if 更有用。

    case ... esac 为多选择语句,与其他语言中的 switch ... case 语句类似,是一种多分支选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case ... esac 语句。

    语法如下

    case value in
    	pattern1)
    		# Command;
    	;;
    	pattern2)
    		# Command;
    	;;
    esac
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一模式匹配,使用星号 * 捕获该值,再执行后面的命令。

    例如

    read -p "输入1-4之间的整数: " num
    case $num in
    	1) echo "你选择了1" ;;
    	2) echo "你选择了2" ;;
    	3) echo "你选择了3" ;;
    	4) echo "你选择了4" ;;
        *) echo "你没有输入1-4之间的数字" ;;
    esac
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.3 循环语句

    这里仅介绍最常用的三种:forwhileuntil

    3.3.1 for

    格式如下

    for var in item1 item2 ... itemN; do
        # Command...
    done
    
    • 1
    • 2
    • 3

    例如

    for num in 1 2 3 4 5; do
    	echo "The number is $num"
    done
    # The number is 1
    # The number is 2
    # The number is 3
    # The number is 4
    # The number is 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们还可以利用 for 循环进行批量移动文件。假设当前目录下有 AB 两个目录,现在需要将目录 A 中的所有 .txt 文件都移动到 B 中,则可以这样做

    for file in $PWD/A/*.txt; do
        mv $file $PWD/B
    done
    
    • 1
    • 2
    • 3

    3.3.2 while

    格式如下

    while condition; do
        # Command...
    done
    
    • 1
    • 2
    • 3

    例如

    x=0
    while [ $x -lt 5 ]; do
    	echo $x
    	x=`expr $x + 1`
    done
    # 0
    # 1
    # 2
    # 3
    # 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    若想使用无限循环,只需将 condition 改为 :true 即可。

    3.3.3 until

    until 循环跟 while 循环正好相反。它跟 while 一样也需要检测一个条件,但不同的是,只要该条件为 false 就一直执行循环。

    格式如下

    until condition; do
        # Command...
    done
    
    • 1
    • 2
    • 3

    例如

    x=0
    until [ $x -ge 5 ]; do
    	echo $x
    	x=`expr $x + 1`
    done
    # 0
    # 1
    # 2
    # 3
    # 4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.3.4 break与continue

    shell 的 breakcontinue 的功能与其他语言一样,具体请参考下面的例子。

    x=0
    while :; do
    	if [ $x -gt 10 ]; then
    		echo $x
    		break
    	else
    		x=`expr $x + 1`
    	fi
    done
    # 11
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    再例如,输出1-10之间的所有偶数

    for x in {1..10}; do
    	if [ `expr $x % 2` -eq 0 ]; then
    		echo $x
    	else
    		continue
    	fi
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    📝 有关括号的使用请参考这篇文章 shell 中各种括号的作用()、(())、[]、[[]]、{}

    3.4 函数

    shell 中函数的定义格式如下

    [function] funname(){
    	# Code...
    	[return int]
    }
    
    • 1
    • 2
    • 3
    • 4

    其中方括号的内容为可选。

    如果函数有返回值的话,则返回值必须是0-255之间的整数。如果不加 return 语句,shell 将默认以最后一条命令的运行结果作为函数的返回值。

    定义一个最简单的函数

    # 定义函数
    demo(){
    	echo "Hello World"
    }
    
    # 执行函数
    demo
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    计算两数之和

    twoSum(){
    	read -p "请输入第一个数: " a
    	read -p "请输入第二个数: " b
    	return `expr $a + $b`
    }
    
    twoSum
    echo "两数之和为: $?"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    函数返回值在调用该函数后通过 $? 来获得。

    3.4.1 位置参数

    位置参数表:

    变量描述
    $0脚本名称
    $1$2、…、$9第1个到第9个参数
    ${10}、…、${N}第10个到第N个参数
    $*$@除了 $0 以外的所有位置参数
    $#不包括 $0 在内的位置参数的个数

    例如

    func(){
    	echo "第一个参数为 $1"
    	echo "第二个参数为 $2"
    	echo "第十个参数为 ${10}"
    	echo "共有 $# 个参数"
    	echo "输入的所有参数: $*"
    }
    
    func 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    # 第一个参数为 3
    # 第二个参数为 4
    # 第十个参数为 12
    # 共有 14 个参数
    # 输入的所有参数: 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    References

    [1] 一篇文章让你彻底掌握 shell 语言
    [2] Shell 教程 | 菜鸟教程

  • 相关阅读:
    Linux综合性命令及解析
    【生成式网络】入门篇(二):GAN的 代码和结果记录
    智能巡检平台哪家好?为什么选择“的修”大数据管理平台
    网工常用工具——tcping
    Tungsten Fabric SDN — 与 Kubernetes 的集成部署(CN)
    Django Channel layers -- 实时应用程序的消息中间件
    『Material Design』CollapsingToolbarLayout可折叠标题栏
    前端AJAX入门到实战,学习前端框架前必会的(ajax+node.js+webpack+git)(二)
    tcpdump简析
    阿里云ESSD云盘IOPS性能如何?
  • 原文地址:https://blog.csdn.net/raelum/article/details/126208050