当前位置 博文首页 > RtxTitanV的博客:Shell编程之常用内置命令

    RtxTitanV的博客:Shell编程之常用内置命令

    作者:[db:作者] 时间:2021-07-07 10:01

    本文主要对常用的Shell内置命令(Shell Builtin Commands)进行简单总结,另外本文所使用的Linux环境为CentOS Linux release 8.2.2004,所使用的Shell为bash 5.1.0(1)-release

    一、alias

    alias [-p] [name[=value]]
    

    不带参数或使用-p选项,alias将以允许作为输入重用的形式在标准输出上打印别名列表。如果提供了参数,并且给出值,则为指定命令定义一个别名;如果没有给出值,则打印出别名的名称和值。

    别名:

    • 别名可以用一个字符串替换简单命令中的第一个单词。Shell维护一个别名列表,可以用内置命令aliasunalias设置和删除别名。
    • 对于每条简单命令中的第一个单词,如果没被引用(unquoted,没加'"\),Shell都会去检查它是否有别名,如果有,则该单词将被别名中的文本替换。
    • /$`=以及任何Shell元字符或引用字符(quoting characters,'"\)都不能出现在别名的名称中;替换文本中可以包含任何有效的Shell输入,包括Shell元字符。
    • 检查别名时只测试替换文本中的第一个单词,但与正在扩展(被替换)的别名相同的单词不会再次扩展(被替换),意思是不会递归的去扩展(替换)要替换的文本,例如可以把ls作为ls -F的别名。
    • 如果别名值的最后一个字符是空格或制表符,则还要检查别名后的下一个命令的单词进行别名扩展。
    • 在非交互式Shell中,除非使用shopt命令设置shell的expand_aliases选项,否则不会进行别名扩展。
    • Bash在执行某一行或复合命令上的任何命令之前,总是读取至少一整行的输入以及复合命令的所有行,而别名是在读取命令时扩展,不是在执行时。所以同一行中另一个命令定义的别名直到读取下一行输入时才会生效,而该行中该别名之后的命令不受它影响。
    • 别名在读取函数定义时扩展而不是在函数执行时,因为函数定义本身就是一个命令,因此,在函数中定义的别名直到该函数执行之后才能使用。
    • 为了安全起见,始终在单独的行上定义别名,并且不要在复合命令中使用别名。

    简单命令:简单命令是最常使用的命令,它只是一个由空白符(空格或制表符)分隔的单词序列,由Shell的一个控制运算符终止。其中第一个单词通常指定要执行的命令,其余单词都是该命令的参数。
    复合命令:复合命令是Shell编程语言结构体,每个结构体都以一个保留字或控制运算符开始,以一个与之对应的保留字或控制运算符结束。复合命令包括循环结构(whilefor等)、条件结构(ifcase(( ))[[ ]]等)和命令组合。
    控制运算符:一种执行控制功能的符号(token),包括换行符和以下之一:||&&、&、;;;;&;;&||&()

    这里以alias不带参数为例,显示当前Shell进程的别名列表:
    1
    下例给命令ps -l创建一个别名pl,创建别名之后通过alias pl打印出了别名的名称和值,然后使用别名pl就可以执行ps -l
    2
    下例给命令ps -j创建一个别名pj,然后又给pj -l创建别名pjl,这里的pjl会被替换为pj -l,而pj -l第一个单词pj也是一个别名,会被替换为ps -j,而ps在这里不是别名,不会被替换,最终使用pjl也执行了ps -jl
    3
    下例给命令ps -l创建一个别名ps,别名ps和命令ps重名,原本的命令ps会被覆盖,因为别名的优先级要高于命令,这时要使用原始的ps,只有使用绝对路径或相对路径来执行,这里的ps会被替换为ps -l,而ps -l的第一个单词与别名ps相同,则ps -l中的ps不会再被替换,即不会递归的去替换要替换的文本:
    4

    别名最好不要和系统命令重名。命令执行时的优先级由高到低:

    1. 用绝对路径或相对路径执行的命令。
    2. 别名。
    3. Bash内置命令。
    4. 按照$PATH环境变量定义的目录査找的第一条命令。

    别名值的最后一个字符是空格或制表符时,还要检查别名后的下一个命令的单词进行别名扩展。这样可以把多个别名组合起来,这里以空格为例:
    5
    不要在复合命令或函数中使用别名。示例如下:

    #!/bin/bash
    
    # 非交互式Shell中要使用别名须用shopt命令设置shell的expand_aliases选项
    shopt -s expand_aliases
    if [ 1 ]
    then
        alias pl='ps -l'
        # 复合命令中定义的别名要在整个命令结束后才生效
        pl
    fi
    pl
    function functest() {
        alias la='ls -laF | grep bash'
        # 函数中定义的别名要在函数执行之后才生效
        la
    }
    functest
    la
    

    执行结果:
    6
    注意在非交互式Shell中要使用别名须用shopt命令设置shell的expand_aliases选项。把上面脚本中的shopt -s expand_aliases去掉后,在Shell脚本中就无法使用别名了,如下所示:
    7
    注意,在代码中使用alias命令定义的别名只能在当前Shell进程中使用,当前Shell进程结束后,别名也会随之消失。可以把别名写入配置文件~/.bashrc中,这些别名对启动时会加载该配置文件的Shell都有效。Shell脚本在执行时可以通过登录以加载配置文件的方式使用配置文件中定义的别名。

    二、unalias

    unalias [-a] [name … ]
    

    从别名列表中删除指定名称的别名,如果提供了-a选项,则删除所有别名。

    删除指定别名的示例如下:
    8
    删除所有别名:
    9
    使用unalias删除只能在当前Shell进程生效,对于已经写入配置文件的别名,要想永久删除就需将配置文件中的别名定义删除。

    三、exit

    exit [n]
    

    退出Shell并将状态n返回给Shell的父进程,如果省略n,则退出状态为最后被执行的命令的退出状态,EXIT陷阱是在Shell终止前执行的。

    退出状态:

    • 被执行的命令的退出状态是系统调用waitpid或与其等价的函数所返回的值。退出状态介于0到255之间,Shell可能会特别使用高于125的值,某些情况Shell将使用特殊值来表示特定的错误状态。
    • 为了便于Shell处理,执行成功的命令退出状态为0,而退出状态非0则表示失败。
    • 如果命令接到一个值为N的关键信号而退出,Bash会使用128+N作为它的退出状态。
    • 如果未找到命令,则为执行它而创建的子进程将返回状态127。如果命令找到但不是可执行的,返回状态126。
    • Bash的条件命令(if[[ ]]等)以及部分命令列表使用了退出状态。
    • 所有Bash内置命令都在成功时返回退出状态0,失败返回非0,如果使用错误(如无效的选项或缺少参数),所有内置命令都会返回退出状态2。

    如下示例脚本在输出hello之后就退出并返回退出状态10:

    #!/bin/bash
    
    echo "hello"
    exit 10
    echo "world"
    

    执行结果和退出状态如下:
    10

    四、echo

    echo [-neE] [arg …]
    

    输出参数,以空格分隔,换行符结束。除非发生写错误,否则返回状态为0。

    选项

    • -e:启用反斜杠\开头的转义字符的解释。
    • -E:禁用转义字符的解释,即使在默认解释转义字符的系统也是如此。
    • -n:结束时不会输出换行符。

    默认情况下可以使用shell选项xpg_echo动态决定echo是否解释转义字符。echo不解释指选项的结束的--

    echo输出默认以换行符结束,使用-n选项可以结束时不换行,示例如下:

    #!/bin/bash
    
    echo "hello"
    echo "world"
    echo -n "ja"
    echo -n "va"
    echo "script"
    echo "shell"
    

    执行结果:
    11
    使用-e可以开启转义字符的解释,-E禁用转义字符的解释,示例如下:

    #!/bin/bash
    
    echo "hello\nworld"
    # 开启转义字符的解释,\n表示换行
    echo -e "hello\nworld"
    # 禁用转义字符的解释
    echo -E "hello\nworld"
    echo "helloworld\c"
    # 开启转义字符的解释,\c表示不继续输出,放在最末尾可以达成不换行效果
    echo -e "helloworld\c"
    # 禁用转义字符的解释
    echo -E "helloworld\c"
    echo "hello\bworld"
    # 开启转义字符的解释,\b表示退格
    echo -e "hello\bworld"
    # 禁用转义字符的解释
    echo -E "hello\bworld"
    

    执行结果:
    12

    五、declare

    # 下面提到的"名称"都是指的这里的name参数
    declare [-aAfFgiIlnrtux] [-p] [name[=value]]
    

    声明变量并赋予它们属性,如果没有给定名称,则显示变量的值。如果变量名称后面有=值,这个值就会被赋给该名称。除非遇到无效选项、试图用-f foo=bar的形式定义函数、试图给只读变量赋值、没有使用复合赋值语法给数组变量赋值、其中某个名称不是有效shell变量名、试图关闭只读状态的只读属性、试图关闭数组变量的数组状态或试图用-f显示一个不存在的函数,否则返回状态为0。

    选项

    • -p:显示每个名称的属性和值。
      • -p和名称参数一起使用时,除-f-F之外的其他选项将被忽略。
      • 如果-p不带名称参数,将显示所有具有由其他选项指定的属性的变量的属性和值。如果给定-p而没提供其他选项,则显示所有Shell变量的属性和值。
      • -f选项限制输出只显示函数。-F选项禁止显示函数定义,只显示函数名和属性。如果使用shopt启用了shell的extdebug选项,则还会显示函数定义所在的源文件名和行号。-F选项隐含了-f
    • -a:每个名称都是一个索引数组变量。
    • -A:每个名称都是一个关联数组变量。
    • -f:只使用函数名。
    • -F:禁止显示函数定义,只显示函数名和属性。
    • -g:强制在全局作用域中创建或修改变量,即使在shell函数中执行declare时也是如此。它在所有其他情况下都被忽略。
    • -i:将变量视为整数,对它赋值时会进行算术求值。
    • -I:会使local变量继承周围作用域中任何同名变量的属性(nameref属性除外)和值。如果没有现有的变量,则local变量初始时是未设置的。
    • -l:当给变量赋值时,所有大写字母都转化为小写。禁用upper-case属性。
    • -n:为每个名称赋予nameref属性,使其成为对另一个变量的名称引用(name reference),另一个变量由该名称的值定义。对名称的所有引用、赋值和属性修改(使用或更改-n属性本身除外)都将对名称的值所引用的变量执行。nameref属性无法应用于数组变量。
    • -r:将名称设为只读,之后这些不能用赋值语句对这些名称赋值或用unset删除。
    • -t:为每个名称赋予trace属性,设置了trace属性的函数继承调用它的shell的DEBUGRETURN
      类型的trap。trace属性对变量来说没有特殊意义。
    • -u:当给变量赋值时,所有小写字母都转化为大写。禁用lower-case属性。
    • -x:通过环境把每个名称导出给后续命令(将变量导出为环境变量)。

    在选项前使用+而不是-会关闭该属性,例外的是+a+A不会销毁数组变量,+r也不会移除只读属性。
    当在函数中使用时,declare使每个名称都是局部可见的,和local命令一样,除非使用了-g选项。
    当使用-a-A和复合赋值语法创建数组变量时,附加属性直到后续赋值才生效。

    declare不指定名称,显示所有具有指定选项属性的变量的值和属性,如果没指定选项,显示所有变量和函数(不显示属性)。

    -f选项限制输出只显示函数。-F禁止显示函数定义,只显示函数名和属性。

    显示所有具有-i-r属性的变量的属性和值以及显示所有函数的名称和属性(下图只截取了部分函数)的示例如下:
    13
    -p可以显示名称的属性和值,带名称参数则显示指定名称的属性和值,不带名称参数,则显示所有具有由其他选项指定的属性的变量的属性和值,如果也没有其他选项则显示所有Shell变量的属性和值。要显示函数需带上-f-F选项。显示所有具有-a属性的(索引数组)变量的属性和值,显示指定变量的属性和值,显示指定函数的定义,显示指定函数的名称和属性以及显示所有函数的名称和属性(下图只截取了部分函数)的示例如下:
    14
    -a-A可以分别声明索引数组和关联数组。示例如下:

    #!/bin/bash
    
    # 声明并创建索引数组
    declare -a array1=([0]="sh" [2]="bash" [4]="csh")
    # 显示array1的属性和值
    declare -p array1
    # 输出数组array1中的所有下标
    echo ${!array1[@]}
    # 输出数组array1中的所有元素
    echo ${array1[@]}
    # 声明并创建关联数组
    declare -A array2=(["name"]="ZhangSan" ["age"]=25 ["tel"]="18912312312")
    # 显示array2的属性和值
    declare -p array2
    # 输出数组array2中的所有下标
    echo ${!array2[@]}
    # 输出数组array2中的所有元素
    echo ${array2[@]}
    

    执行结果:
    15
    -i可以将声明的变量视为整数,对它赋值时会进行算术求值,仅支持最基本的数学运算。示例如下:

    #!/bin/bash
    
    declare -i result1 result2 result3 result4 result5 result6 result7 result8
    m=5
    n=3
    result1=${m}+${n}
    result2=m-n
    result3=${m}*${n}
    result4=m/n
    result5=${m}%${n}
    result6=m**n
    # 需要用双引号包围,或在小括号前加\转义,形如\(${m}+${n}\)/\(${m}-${n}\),不然会报错
    result7="(${m}+${n})/(${m}-${n})"
    # 需要用引号(这里单引号和双引号都可以)包围或在小括号前加\转义,不然会报错
    result8="(m+n)*(m-n)"
    echo "m:${m},n:${n}"
    echo "m + n : ${result1}"
    echo "m - n : ${result2}"
    echo "m * n : ${result3}"
    echo "m / n : ${result4}"
    echo "m % n : ${result5}"
    echo "m ** n : ${result6}"
    echo "(m + n) / (m - n) : ${result7}"
    echo "(m + n) * (m - n) : ${result8}"
    

    执行结果:
    16
    -l-u可以在变量赋值时进行大小写转换。示例如下:

    #!/bin/bash
    
    # 给变量赋值时将所有大写字母转为小写
    declare -l var1="HelloWorld"
    echo ${var1}
    # 给变量赋值时将所有小写字母转为大写
    declare -u var2="HelloWorld"
    echo ${var2}
    

    执行结果:
    17
    -n为每个名称赋予nameref属性,使其成为对另一个变量的名称引用。对名称引用的赋值和属性修改(除-n属性本身)都会对它所引用的变量执行。示例如下:

    #!/bin/bash
    
    a="hello"
    # 给ref1赋予nameref属性,成为对变量a的名称引用
    declare -n ref1="a"
    # 给ref2赋予nameref属性,成为对变量ref1的名称引用
    declare -n ref2="ref1"
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    # 给ref1赋值将对它所引用的变量a赋值
    ref1="sh"
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    # 给ref2赋值将对它所引用的变量ref1赋值,而给ref1赋值又将对它所引用的变量a赋值
    ref2="bash"
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    # a没有被声明为整数,赋值时不会进行算术求值
    a=5+3
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    # 将ref1声明为整数,实际上是将它引用的变量a声明为整数
    declare -i ref1
    a=5+3
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    # 将ref2声明为只读变量,实际上是将它所引用的变量ref1声明为只读,ref1又引用a,最终将a声明为只读变量
    declare -r ref2
    # 由于a为只读变量,所以不能被修改
    a=3
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    # 关闭ref1和ref2的nameref属性
    declare +n ref1 ref2
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    ref1=5
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    ref2=2
    echo "a : ${a}, ref1 : ${ref1}, ref2 : ${ref2}"
    

    执行结果:
    18
    虽然nameref属性无法应用于数组变量,但被赋予nameref属性的变量可以引用数组变量或数组元素的变量。示例如下:

    #!/bin/bash
    
    declare -a array1=(1 2 3)
    # nameref属性无法应用于数组变量
    declare -an arrayref1="array1"
    # 查看arrayref1会发现其没有nameref属性
    declare -p arrayref1
    # nameref属性变量可以引用数组变量
    declare -n arrayref2="array1"
    declare -p arrayref2
    # nameref属性变量也可以引用数组元素
    declare -n arrayref3="array1[1]"
    declare -p arrayref3
    # arrayref1没有nameref属性,对其赋值并不会影响数组array1
    
    下一篇:没有了