shell脚本基础

9. Shell脚本基础

9.1 概述

​ Shell是用户与Linux操作系统沟通的桥梁,用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操作。在Linux GUI日益完善的今天,在系统管理等领域,Shell编程仍然起着不可忽视的作用。

​ Linux的Shell种类众多,常见的有:Bourne Shell(/usr/bin/sh或/bin/sh)、Bourne Again Shell(/bin/bash)、C Shell(/usr/bin/csh)、K Shell(/usr/bin/ksh)、Shell for Root(/sbin/sh),等等。不同的Shell语言的语法有所不同,虽然大同小异,但每种Shell都有其特色之处,所以不能保证交换使用。在本文中,我们关注的重点是Bash,也就是Bourne Again Shell,由于易用和免费,Bash在日常工作中被广泛使用;同时,Bash也是大多数Linux系统默认的Shell。前面介绍过的一些Shell使用技巧就是针对Bash的。在一般情况下,并不需要区分 Bourne Shell和Bourne Again Shell,大多数Linux发行版中/bin/sh经常是链接到/bin/bash。

Shell的作用有两方面:

  • 一是为用户提供交互式命令行操作界面,
  • 二是作为一个脚本语言解释器去执行脚本。用户最常见使用Shell的形式是交互式Shell,Shell进程逐行读取用户输入的命令,执行命令产生结果,然后再等待用户输入下一条命令。当作为脚本语言解释器时,会读取特定的文本文件(即脚本)内容,逐条执行其中的命令。

​ Shell脚本不需要特别的开发工具,使用任何一款文本编辑器即可编写,执行时由Shell解释执行。Shell脚本并不是简单的多条命令批量执行,像其他编程语言一样,Shell脚本中可以使用变量,可以使用循环、分支、子程序等控制结构,借助于管道等机制,可以组合实现各种功能。

9.2 理解Shell执行命令的方式

相对于前面使用交互式Shell的简单方式,这里首先介绍一些在Shell中执行命令的不同方法,以便对Shell执行命令的方式有更深入的了解。

9.2.1 命令列表和子Shell中执行命令

  1. 命令列表:Shell允许在一个命令行中可以输入多条命令,方法就是使用“;”进行分隔,Shell会依次执行这些命令,这种机制被称为命令列表

    例如:在一行中输入如下命令:

    • 最终的结果与依次输入cd /etc/X11和ls是一样的。
    • 所以简单的命令列表可以实现多个命令的批量执行。

    img

  2. 子shell:

    用户输入的所有命令都是由Shell进程解释运行的,默认就是由提供命令行输入的Shell解释,但是应该了解,也可以由一个新的Shell进程(被称为子Shell)解释执行命令,理解这一点对于理解Shell脚本的运行有很大帮助。

    在命令行中,Shell也提供了方法让用户指定在子Shell中执行命令,就是使用圆括号把命令括起来

    例如:

    img

粗看起来,这个命令的执行和前面命令列表的执行结果是一样的,但是如果仔细观察的话,会发现前一种情况执行后,当前目录改变了,而后一种情况下当前目录并未改变

这种区别的原因就是:

  • 后一种情况下,为了执行括号中的命令新创建了一个Shell进程,在该进程依次执行了两条命令,执行前面cd命令时,确实改变了当前目录,
  • 但是改变的是新建Shell进程的当前目录(注意:当前目录的概念对于每一个进程是独立的),
  • 执行后面ls命令时也列出的是当前目录的内容,但是ls命令执行完后,新建Shell进程完成了自己的使命以后退出,这时又回到原来的Shell进程提供的交互界面,此Shell进程的当前目录并未改变。

Shell脚本执行时,默认都是由新的子Shell执行的,脚本执行完子Shell进程就会退出,理解了这一点就会很容易理解后面要介绍的export和source命令。

9.2.2 命令的返回值

​ 无论在Shell脚本中还是交互式Shell中,任何一个命令的执行都要由Shell进程启动,Shell会为该命令的执行创建新的进程,进程结束后都会有一个返回值,这个返回值会被Shell捕获,以了解该命令是否正确完成。

  • 在交互式Shell中,如果逐条输入命令,返回值一般都会被忽略,
  • 但是如果在Shell脚本中要执行多条相互关联的命令,就很有可能要根据前一个命令的返回值决定下一个命令执行与否。

要获知一个命令执行的返回值,可以使用一个特殊的Shell变量$?进行访问。关于Shell变量后面还要进一步介绍,此处只要简单知道可以通过命令echo $?显示前一个命令的返回值,一般约定返回0代表成功,非零值代表失败

看一个简单的例子:

img

ls命令成功列出了指定文件信息,下一条的echo $?命令输出的是ls命令执行的结果,为0表示成功。再看一个例子:

img

当指定一个不存在的文件名,ls命令执行失败,接下来的echo $?命令输出1表示ls失败;注意,当再一次执行echo $?命令时,输出为0表示的是前一条echo命令执行成功。

9.2.3 根据返回值执行后续命令

​ 前面介绍的命令列表中,用“;”分隔的两条命令会无条件地依次执行。Shell提供了另外的机制,允许后面命令根据前面命令的执行结果有条件的运行。与C语言中逻辑操作符类似,引入&&和||两个符号:

  • 如果是&&连接两条命令,则前一条命令执行成功后才执行后一条命令,否则不执行;
  • 如果是||连接两条命令,则前一条命令执行失败才执行后一条命令,否则不执行。

使用&&的例子如下:

img

img

使用||的例子如下:

img

9.3 基本的Shell脚本

9.3.1 第一个Shell脚本

  1. 使用任何文本编辑器编辑一个文本文件hello.sh

    后缀名用.sh以表示这是一个Shell脚本,其实这种命名风格并没有实际作用(不影响脚本的编写和执行),只是便于我们从命名知道文件用途。

    #!/bin/bash
    # first shell script
    HELLO="Hello World !"
    echo $HELLO
    
    • 第一行中以“#!”开头是一个约定标记,指定此脚本文件需要用哪个解释器运行,本例中要使用bash;
    • 第二行以“#”开头是注释,会被解释器忽略,脚本中只能使用单行注释;
    • 第三句是对一个Shell变量赋值,
    • 第四句将此Shell变量的值显示输出
  2. 执行脚本文件有两种方法:

    1. 作为可执行程序执行

      让一个脚本文件可执行,需要设置x权限。通过cd命令进入到文件所在目录,依次输入如下命令:

      chmod +x ./hello.sh
      ./hello.sh		#执行hello.sh脚本,结果是输出“Hello World !”
      

      注意:

      • 一定要输入./hello.sh,表示执行当前目录下的hello.sh,
      • 而不能直接输入hello.sh,这样Linux系统会去PATH里寻hello.sh,
      • 但当前目录通常不在PATH里,所以会找不到命令
    2. 作为解释器参数

      这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

      /bin/sh hello.sh
      #这种方式运行的脚本,不需要在第一行指定解释器信息,即使写了也不会起作用。 
      

9.3.2 使用变量

  1. 自定义变量:

    Shell脚本中可以自定义变量

    #语法:【注意:变量名和等号之间不能有空格】
    variable_name= variable_value
    

    变量名的命名须遵循如下规则:

    • 首个字符必须为字母(a-z,A-Z)。
    • 中间不能有空格,可以使用下划线(_)。
    • 不能使用标点符号。
    • 不能使用bash的关键字(可用help命令查看保留关键字)
  2. 引用变量:

    #引用一个定义过的变量,只要在变量名前面加美元符号即可
    $variable_name
    
    #可以在引号中使用【推荐给所有变量加上花括号,这是个好的编程习惯。】
    ${variable_name}
    

    如果变量引用可能引起混淆,可以在变量名外面加花括号以帮助解释器识别变量的边界。比如下面这种情况:

    for skill in Ada Coffe Action Java; do
      echo "I am good at ${skill}Script"
    done
    
    #如果不给skill变量加花括号
    echo "I am good at $skillScript"
    #解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。
    
  3. 删除变量:

    #语法是:	变量被删除后不能再次使用
    unset variable_name
    
  4. 变量分类:

    Shell脚本中有三种变量:

    • 局部变量:在脚本或命令中定义,仅在当前Shell实例中有效,其他Shell启动的程序不能访问局部变量。
    • 环境变量:Shell启动的程序都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
    • Shell变量:由Shell程序设置的特殊变量,有一部分是环境变量,有一部分是局部变量,这些变量保证了Shell的正常运行

9.3.3 控制结构

Shell脚本中可以使用各种流程控制语句,也可以自定义函数。此处我们只关心基本的流程控制语句写法。

  1. if

    #语法格式:
    if condition
    then
      command1
      command2
      ...
    fi
    
    #末尾的fi就是if倒过来拼写,后面还会遇到类似的。
    #如果将if语句写成一行,语法格式:
    if condition; then command1; command2; ...; fi
    
  2. if else

    #语法格式:
    if condition
    then
      command1
      command2
      ...
    else
      command
    fi
    
  3. if else-if else

    #语法格式:
    if condition1
    then
      command1
    elif condition2 
    then 
      command2
    else
      commandN
    fi
    
    #实例:判断两个变量是否相等:
    a=10
    b=20
    
    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
    
  4. for循环

    语法格式: 
    
    for var in item1 item2 ... itemN
    
    do
    
      command1
    
      command2
    
      ...
    
    done
    
    写成一行: 
    
    for var in item1 item2 ... itemN; do command1; command2… done;
    
    当变量值在列表里,for循环即执行一次所有命令,使用变量名获取列表中的当前取值;命令可为任何有效的shell命令和语句;in列表是可选的,如果不用它,for循环使用命令行的位置参数。 
    
    例如,顺序输出当前列表中的数字:
    
    for loop in 1 2 3 4 5
    
    do
    
      echo "The value is: $loop"
    
    done
    
    输出结果: 
    
    The value is: 1
    
    The value is: 2
    
    The value is: 3
    
    The value is: 4
    
    The value is: 5
    
  5. while循环

    while循环用于不断执行一系列命令,也用于从输入文件中读取数据;

    #语法格式为:
    while condition
    do
      command
    done
    
    #下面的例子中,输入信息被设置为变量FILM,按<Ctrl-D>结束循环。 
    echo 'Press <CTRL-D> to exit'
    echo -n 'Input your favorite movie: '
    
    while read FILM
    do
      echo "YES! $FILM is a great movie."
    done
    
    #运行脚本,输出结果类似下面: 
    Press <CTRL-D> to exit
    
    Input your favorite movie: 2046
    YES! 2026 is a great movie.
    
  6. 无限循环

    无限循环中一般需要调用特定命令以终止该层次的循环。

    Shell提供两个命令来实现该功能:break和continue。

    • break命令允许跳出所有循环(终止执行后面的所有循环);
    • continue命令仅仅跳出当前循环。
    • 此外,还可以在循环中使用exit命令直接终止脚本的执行。
    #语法格式:
    while :
    do
      command
    done
    
    #或者 
    while true
    do
      command
    done
    
    #或者 
    for (( ; ; ))
    do
      command
    done
    
  7. until循环

    until循环与while循环在处理方式上刚好相反,执行一系列命令直至条件为真时停止。

    #语法格式:
    until condition
    do
      command
    done
    
    #条件测试发生在循环末尾,因此循环至少执行一次。
    
  8. case语句

    case语句为多选择语句,可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。

    • case语句取值后面必须为单词in,
    • 每一模式必须以右括号结束。
    • 取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。
    • 一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。
    • 如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
    #case语句格式如下: 
    case 值 in
    模式1)
      command1
      ;;
    模式2)
      command2
      ;;
    esac 
    

9.4 Shell变量

前一节中介绍了Shell变量的基本用法,除了在自己定义并使用变量,还有一些不需要我们定义的特殊变量可以直接使用,而我们自己定义的Shell变量也可以导出为环境变量从而在Shell脚本之外产生作用。

9.4.1 Bash变量

当Shell脚本被执行时,Bash会自动设置一些变量,这些变量对于脚本程序而言是只读的,但是其中包含的信息可能非常有用,比如可以通过这些变量获得命令行参数信息。常见的Bash变量如下表所示:

变量引用名称 含义
$# 传递到脚本的参数个数
\(* | 以一个单字符串显示所有向脚本传递的参数。 如"\)*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
\(@ | 与\)*相同,但是使用时加引号,并在引号中返回每个参数。 如"$@"用「"」括起来的情况、以"$1" "\(2" … "\)n" 的形式输出所有参数。
$- 显示Shell使用的当前选项
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
$PPID 当前进程的父进程的ID号
$SHELLOPTS 冒号分隔的Shell当前选项
$UID 当前进程用户的userid
$n | 获取命令行参数。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……。$0 为执行的文件名。

除了上述只读的Bash变量,还有部分变量由Bash初始化,但是在脚本中可以重新赋值,部分此类变量如下表所示:

变量名称 含义
BASH_VERSION 当前bash版本号
OLDPWD 之前的工作目录
PWD 当前工作目录
RANDOM 0~32767之间的一个随机数
SECONDS 当前Shell启动后经过的秒数
PS1 基本提示符,默认情况下,root用户#结尾,普通用户$结尾
PS2 附属提示符,默认是“>”

9.4.2 环境变量

Shell变量分为本地变量和环境变量,本地变量在用户现有运行的脚本中使用,环境变量可以在所有的子进程中使用。两者的本质区别在于保存位置不同:

  • 本地变量保存在当前Shell进程中,离开当前进程变量就不存在了;
  • 环境变量托管给内核作为当前进程内核数据的一部分,当创建子进程时,环境变量会复制给子进程从而可以被子进程访问到。

环境变量:本身只是负载着一些全局性的信息,真正产生作用还是需要进程读取环境变量的值并作出相应的处理。

  • 举例来说,很多人都知道Linux下有名为PATH的环境变量,修改变量的值会影响执行文件的搜索路径,但是之所以会产生影响,是因为Shell会读取这个变量并根据其值搜索不同目录寻找是否存在命令文件。
  • 再举一个例子:LANG环境变量会影响进程的locale设置,对于国际化的软件其执行结果就会产生影响,如果执行ls -l命令时,列出的时间是中文表示,说明当前locale是中文的,可以通过执行命令 LANG=en_US ls -l 看到不一样的列表;反之如果执行ls -l命令时,列出的时间是英文表示,可以通过执行命令 LANG=zh_CN ls -l 看到不一样的列表;改变的原因就是改变了LANG的值。

本地变量:可以通过export命令导出成环境变量,实际上所有的环境变量都是通过这种方式产生的,只是这些系统的环境变量一般是在某个Shell初始化脚本中使用export导出的,Shell初始化相关脚本后面会有介绍。

部分常见环境变量的名称及含义如下表所示:

变量名称 含义
PATH 决定了Shell将到哪些目录中寻找命令或程序
HOME 当前用户主目录
LOGNAME 当前用户的登录名
HOSTNAME 主机的名称
SHELL 当前用户Shell类型
LANG 为国际化程序指定语言和区域信息

9.4.3 相关命令

下面说明几个与Shell变量相关的命令,命令细节不再赘述。

  1. env命令

    env 和 printenv这两个命令用于打印所有的环境变量。

  2. set命令

    用于显示与设置当前环境的所有的变量,它包括环境变量和一些本地变量,还包括一些自定义Shell特性,如Shell函数等。

    与set命令相关的还有unset命令,用于清除变量。

  3. export

    用于把变量变成环境变量,有效期是当前Shell及其子进程,因此退出当前Shell后,它所设定的环境变量就消失了。

10.5 Shell运算符和表达式

Shell 和其他编程语言一样,支持多种运算符,包括:

  • 算数运算符、关系运算符、布尔运算符、字符串运算符、文件测试运算符等;
  • 另外在表达式求值时需要用到一些求值和测试条件相关命令。

9.5.1 命令替换

命令替换(Command Substitution)是指 Shell 执行指定命令并使用执行该命令后的结果替换原来内容(即命令替换部分)。

命令替换有两种方式:使用反引号“`”和使用$()

  • 两种方式的效果基本上是一样的,区别在于不同的Shell对其支持程度,另外在转义字符处理方面有一定区别。
  • POSIX规范建议使用$(命令)的形式,因为更易读,而且转义处理比较简单,
  • 但是因为最早引入命令替换功能的形式是反引号,所以在不同Shell中更通用,很多人习惯性地使用。

下面是一个简单的例子,可以在命令行直接输入:

echo `pwd`
#或者
echo $(pwd)

#可以观察到两者执行效果是一致的,都是输出了pwd命令的执行结果。

9.5.2 相关命令

  1. expr命令:

    用于简单的整数计算和字符串操作。

    #语法格式:
    expr expression
    
    #整数计算例子:
    expr 14 % 9     #结果为:5
    expr 10 + 10    #结果为:20
    expr 1000 - 900    #结果为:100
    expr 1000 / 10     #结果为:100
    expr 10 \* 100   #结果为:1000 (使用*时,需用\进行转义)
    
    #字符串操作例子:
    expr length "this is a test"   	#结果为:14 (计算字符串长度)
    expr substr "this is a test" 3 5 	#结果为:is is (抓取子字符串)
    expr index "sarasara" a  	#结果为:2(获取第一个字符出现的位置)
    
    #当用于整数计算时,Shell提供与expr命令功能类似的另一种写法:
    $(( expression ))
    
  2. eval命令

    eval命令用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描。

    #语法格式:
    eval command 
    
    #两次扫描的例子如下,执行包含下面命令的在脚本文件:
    eval echo \$$#
    
    #执行结果应该是:显示最后一个命令行参数,如果没有命令行参数则显示脚本名。
    #其工作原理是:
    #	第一遍扫描时“\$”替换为$(\作为转义字符),剩下的“$#”替换为命令行参数的个数(假设为2);
    #	第二次扫描时,变为执行“echo $2”,即显示最后一个参数。
    
  3. test命令和 【 命令

    test命令评估一个表达式,然后返回真或假。

    • 一般和 if、while 或 until 命令结合使用,可以对程序流进行广泛的控制。
    • test命令有一个更常用的别名:[ (左方括号),这两个命令是等价的。
    #语法格式:
    test expression
    
    [ expression ]
    

    当使用左方括号而非 test 时,其后必须始终跟着一个空格、要评估的条件、一个空格和右方括号。

    • 右方括号不是任何东西的别名,而是表示所需评估参数的结束。
    • 条件两边的空格是必需的,这表示要调用 test,以区别于同样经常使用方括号的字符/模式匹配操作。

    需要说明,新的Shell又引入一个 [ 命令的扩展表示方法,就是使用 [[ 以及对应的 ]] 结束。

    • 扩展的测试命令 [[ 允许其中的判定表达式写起来更直观,
    • 包括可以使用“>”、“<”符号作为关系运算符、
    • 允许比较字符串、可以使用&&、||等逻辑操作符等。

9.5.3 常见运算符

此处所说的常见运算符指与其他编程语言中类似的运算符,包括算数运算符、关系运算符、布尔运算符、逻辑运算符等。因为比较简单,所以只是简单介绍一下相关算符及其含义,不再举例说明。

  1. 算术运算符

    常见的算术运算符包括:+、-、*、/、%等,其含义与C语言中类似。

    需要说明,Shell不支持数学运算,需要通过其他命令来实现,最常用的是expr。

  2. 关系运算符

    关系运算符用于比较两个变量或常量之间的数值关系,只支持数字,不支持字符串,除非字符串的值是数字。常见关系运算符及其含义如下:

    • -eq 检测两个数是否相等,相等返回 true。
    • -ne 检测两个数是否相等,不相等返回 true。
    • -gt 检测左边的数是否大于右边的,如果是,则返回 true。
    • -lt 检测左边的数是否小于右边的,如果是,则返回 true。
    • -ge 检测左边的数是否大等于右边的,如果是,则返回 true。
    • -le 检测左边的数是否小于等于右边的,如果是,则返回 true。
  3. 布尔运算符

    布尔运算符及其含义如下:

    • ! 非运算,表达式为 true 则返回 false,否则返回 true。
    • -o 或运算,有一个表达式为 true 则返回 true。
    • -a 与运算,两个表达式都为 true 才返回 true。
  4. 逻辑运算符

    逻辑运算符及其含义如下:

    • && 逻辑的 AND
    • || 逻辑的 OR

    需要说明,逻辑运算符应用于判断表达式中,只能使用 [[ ]] 方式,用test和 [ ] 方式都不支持。

9.5.4 字符串运算符

字符串运算符应用在字符串上,常见运算符及其含义如下:

  • = 检测两个字符串是否相等,相等返回 true。
  • != 检测两个字符串是否相等,不相等返回 true。
  • -z 检测字符串长度是否为0,为0返回 true。
  • -n 检测字符串长度是否为0,不为0返回 true。

9.5.5 文件运算符

文件测试运算符用于检测 Unix 文件的各种属性,常见的属性检测如下:

  • -b file 检测文件是否是块设备文件,如果是,则返回 true。
  • -c file 检测文件是否是字符设备文件,如果是,则返回 true。
  • -d file 检测文件是否是目录,如果是,则返回 true。
  • -f file 检测文件是否是普通文件,如果是,则返回 true。
  • -g file 检测文件是否设置了 SGID 位,如果是,则返回 true。
  • -k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。
  • -p file 检测文件是否是具名管道,如果是,则返回 true。
  • -u file 检测文件是否设置了 SUID 位,如果是,则返回 true。
  • -r file 检测文件是否可读,如果是,则返回 true。
  • -w file 检测文件是否可写,如果是,则返回 true。
  • -x file 检测文件是否可执行,如果是,则返回 true。
  • -s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。
  • -e file 检测文件(包括目录)是否存在,如果是,则返回 true。

9.5.6 综合性例子

综合利用上面介绍的知识,我们可以编写一些简单的Shell脚本完成一些基本的功能。

  1. 查看目录信息

    #编写如下内容的Shell脚本文件,运行查看效果并分析其功能。
    \#!/bin/sh
    if [ $# != 1 ]
    then
     echo "Usage: $0 <dir>"
     exit
    fi
    
    if [ ! -e $1 ]
    then
     echo $1 does not exist!
     exit
    fi
    
    if [ ! -d $1 ]
    then
     echo $1 is not a directory!
     exit
    fi
    
    echo "There are "$(ls $1 | wc -w)" entries in $1"
    echo "Size of $1 is "$(du -sh $1 | cut -f1)"."
    
  2. 查看命令行参数信息

    #编写如下内容的Shell脚本文件,运行查看效果并分析其功能。
    \#!/bin/sh
    echo "There are "$#" parameters."
    i=1
    
    for arg in $*
    do
     echo "parameter $i is "$arg"."
     i=$(expr $i + 1)
    done
    
  3. 交互查看Shell变量

    #编写如下内容的Shell脚本文件,运行查看效果并分析其功能。
    \#!/bin/sh
    unset var
    
    while [ "$var" != "end" ]
    do
     echo -n "please input name of variable start with $: "
     read var
     if [ "$var" = "end" ]
     then
      break
     fi
     echo "$var = $(eval echo $var)"
    done
    

9.6 重新认识Shell

了解一些Shell脚本相关知识后,对于交互式Shell也会有更深入的认识。

9.6.1 Shell命令行扩展

Shell命令行中有一些特殊符号会先被扩展然后解释执行,之前章节已经涉及部分扩展方式,这些扩展有的与Shell变量相关,有的无关,在此再做一次集中摘要介绍。

  1. 命令历史扩展

    在命令行输入 !n 将会由Shell自动扩展成命令历史记录中的第n条。

  2. 花括号扩展

    Shell会自动把 {} 中的逗号分隔列表扩展成几个不同的项,分别执行命令行指定的命令,这样可以用一条命令实现批量操作。

    例如,mkdir dir{1,2,3}命令将会创建dir1、dir2、dir3三个目录。

    在一个命令行中运用多个 {} 扩展将会按选项全部组合实现扩展。

  3. 波浪号扩展

    在命令行参数中使用 ~ 符号,Shell会自动扩展为当期用户主目录的路径;

    如果使用“~用户名”,则会扩展成相应用户主目录的路径。

  4. Shell变量扩展

    Shell会把\(variable_name或\){variable_name}扩展成相应Shell变量的值

  5. 算术扩展

    在命令行使用$((表达式)),会计算表达式,扩展成得到的结果值。

  6. 命令替换

    在命令行使用 "反引号"命令“反引号”或 $(命令),Shell会在新建的子Shell中运行相应命令,并扩展成该命令的输出结果。

  7. 文件名通配符

    主要是几种可以在文件名检索时使用的通配符:*、?、[...]、[^...]

  8. 引号和转义符

    命令行中经常会出现需要用引号的情况,例如文件名中有空格。

    Shell对于两种引号中间的字符串解释方法有所区别:

    • '...' 中间的所有字符Shell都不解释;
    • "..." 中间的内容Shell一般不解释,但下面几个字符例外:$ 、 ! 、 ` 、 \,如果希望针对这几个字符也不做扩展,就需要使用转义字符 \ ,转义字符后面的字符Shell将不做解释。

9.6.2 导入执行Shell脚本和Shell初始化

Shell脚本的编写和执行方法,下面要介绍一些特殊的脚本执行。

  1. source命令

    前面已经说明,直接执行一个脚本的时候,其实是在一个子Shell中运行的,脚本执行完后子Shell自动退出,子Shell中所有操作的结果(例如定义或修改的环境变量)都将随之消失。

    如果我们希望脚本的执行结果影响到当前Shell,就需要由当前Shell直接解释执行脚本,而不是新创建一个子Shell去执行脚本。

    Shell的内置命令source用于实现这个功能,source命令还有一个别名:“.”(点命令),其格式如下:

    source 脚本文件名
    . 脚本文件名
    
    #source(或点)命令通常用于重新执行刚修改的初始化脚本。
    
  2. Shell初始化

    当任何一个Shell进程创建后都要进行初始化,所谓初始化实际就是用source方式执行一些特定的脚本,但是不同的Shell类型会执行不同的脚本文件,所以首先需要区分两种不同类型的Shell:登录Shell和非登录Shell。

    • 登录Shell是当用户成功登录后自动创建的Shell进程(实际是login进程根据/etc/passwd文件中的信息创建),
    • 其他方式再创建的Shell进程都是非登录Shell,例如在图形界面下打开一个虚拟终端窗口时创建的交互式Shell,以默认方式执行Shell脚本时创建的非交互子Shell等。

    与Shell初始化相关的脚本文件包括:

    • /etc/profile :主要用来设置所有用户都需要并一致的环境变量,用户登录成功后执行一次;
    • /etc/bashrc :主要用来设置所有用户都需要并一致的环境变量,每次新建Shell进程执行一次;
    • ~/.bash_profile :主要用来设置特定用户需要的环境变量,用户登录成功后执行一次;
    • ~/.bashrc :主要用来设置特定用户需要的环境变量,每次新建Shell进程执行一次。

    上面所列这些文件的位置和名字都是约定好的,特定类型的Shell初始化时会以source方式执行其中部分或全部脚本。所有这些文件都是同样语法的Shell脚本文件,主要的作用就是定义一些Shell变量、导出一些环境变量,之所以要分到这些不同文件中,就是要针对不同情况执行不同集合。

    对于登录Shell,初始化的执行分两条线索:

    1. source方式执行/etc/profile,/etc/profile执行时会自动查找/etc/profile.d目录,然后以source方式执行所有找到的*.sh文件;
    2. source方式执行/.bash_profile,/.bash_profile执行时会以source方式执行/.bashrc,/.bashrc执行时又会以source方式执行/etc/bashrc。

    非登录Shell初始化时:

    1. 从执行~/.bashrc开始,
    2. 进而执行/etc/bashrc。

    此外:

    • 登录Shell退出时会执行~/.bash_logout,
    • 非登录Shell退出时不会再执行任何脚本。

    了解Shell初始化过程的意义在于,当需要通过修改环境变量改变自己的操作环境时(例如修改语言选项),知道应在在哪个文件中修改才是合适的。

posted @ 2024-06-16 19:31  Sunflower0312  阅读(69)  评论(0)    收藏  举报