Shell特殊变量

Friday, December 20, 2019

shell中提供了一些特殊的变量方便在日常和脚本中使用

变量意义
0 ($0)shell名称或执行脚本文件名称
digit ($1~n)传入的参数,依次是$1、$2…$n
* ($*)所有传入参数,"$*" 相当于 “$1 $2 …” (是 “$*” 而非 $*)
@ ($@)所有传入参数,"$@" 相当于 “$1” “$2” “…” (是 “$@” 而非 $@)
# ($#)传入的参数数量
? ($?)上一个程序或命令的退出状态码
_ ($_)执行的上一条命令的最后一个参数
$ ($$)当前进程的PID
! ($!)最近一个被放到后台运行的进程的PID
- ($-)当前shell已设置的选项标志,很少使用
IFSshell内部域分隔符( The Internal Field Separator),用来分割文本字符串

:实际上在 “Bash Reference Manual” 的描述里,上表中只有 IFS 被称为 “special variable” 其他都被称为 “special parameter”

下面进行具体的讲解和演示

  • 0 ($0) , $ ($$)
# 查看当前shell名称
$ echo $0
/bin/bash

# 看看当前shell的PID
$ echo $$
1335

# 新建子shell再看看
$ bash
$ echo $0
bash
$ echo $$
27334
  • digit ($1~n), # ($#), * ($*), @ ($@) ($* 和 $@ 的深入讨论我放到了最后

把下列代码保存为 test.sh

#!/usr/bin/bash

echo "\$0:" $0
echo "\$1:" $1
echo "\$3:" $3
echo "\$#:" $#
echo "\$*:" "$*"
echo "\$@:" "$@"

添加可执行权限并执行

$ chmod +x test.sh
$ ./test.sh a b c d e f g

执行结果

$0: ./test.sh
$1: a
$3: c
$#: 7
$*: a b c d e f g
$@: a b c d e f g

需要注意的是参数较多的时候,比如要引用第11个参数应该用 ${11} 而不是 $11

  • ? ($?)

命令或程序顺利执行结束,正常退出时,退出状态码一般是0,执行出错时根据原因返回其他数值

$ mkdir test
$ echo $?
0

# 手抖打错文件夹名称
$ cd tset
bash: cd: tset: No such file or directory
$ echo $?
1

$ cd test

# 再次手抖
$ touc test.sh
bash: touc: command not found
$ echo $?
127

当然这只是约定,具体在什么情况下返回什么值完全由程序自己决定

#include <stdio.h>

int main(int argc, char *argv[])
{
    puts("Hello world!");
    return 214; //执行结束后返回214作为退出状态码
}

将上述代码保存为 hello.c ,然后编译运行

# 编译
$ gcc hello.c -o hello
# 运行
$ ./hello 
Hello world!
$ echo $?
214
  • ! ($!)

在shell中把程序放到后台运行有两个方法:

  1. 使用 bg 命令
  2. 输入命令时最后加上 &

首先我们需要两个终端,各自执行 ps , 我们需要的是 TTY 字段

这两个终端一个用来执行命令(pts/2),一个用来显示输出信息(pts/3),在“pts/2”中执行以下命令

# 最后的 & 表示将该命令放到后台执行
$ ping localhost > /dev/pts/3 & 

执行后会在“pts/2”中显示该进程的PID

现在 ping 在后台运行,“pts/3”不断显示输出结果

# 看看 $! 的显示结果
$ echo $!
14564

# 用ps确认一下PID是否一致
$ ps 
  PID TTY          TIME CMD
14500 pts/2    00:00:00 bash
14564 pts/2    00:00:00 ping
14618 pts/2    00:00:00 ps

# 杀死该进程
$ kill $!

全过程如下

  • _ ($_)

$_ 保存了上一条命令最后一个参数,使用起来也没什么太多可说,在不小心打错命令名的时候还是挺方便的

$ echo a b c d e f
a b c d e f

$ echo $_
f

# 日常手抖
$ dc  .config/GIMP/2.10/scripts/
dc: Will not attempt to process directory .config/GIMP/2.10/scripts/

$ cd $_

$*和$@的区别

默认情况下,不加双引号的话 $*$@ 没有区别,但我推荐无论何时使用这两个变量都应该加上双引号,即使用 "$*""$@"

"$*" 相当于 "$1 $2 ... $n"

"$@" 相当于 "$1" "$2" "..." "$n"

单纯的 echo 看不出区别,需要放到 for 语句中

#!/usr/bin/bash

echo "use \$*"
for x in "$*"
do
    echo "$x"
done

echo ""
echo "use \$@"
for x in "$@"
do
    echo "$x"
done

执行结果

$ ./test.sh 1 2 3 4

use $*
1 2 3 4

use $@
1
2
3
4

而实际上 "$*" 还会受到另一个变量 IFS 的影响,IFS 在shell脚本中也十分常见,用来告诉shell怎么分割字符串。对于 IFS 深入讲解的话恐怕又是一篇博客,这里你只需要知道默认 IFS=$' \t\n' ,即空格、制表符和换行符。"$*" 实际上相当于 "$1c$2c...c$n" , 而 c 则是 IFS 变量的第一个字符,即空格。下面我们修改上面的脚本看看差别

#!/usr/bin/bash

#修改IFS变量
IFS=- 

echo "use \$*"
for x in "$*"
do
    echo "$x"
done

echo ""
echo "use \$@"
for x in "$@"
do
    echo "$x"
done

执行结果

$ ./test.sh 1 2 3 4

use $*
1-2-3-4

use $@
1
2
3
4

参考:

shell

一些毫无卵用的命令行工具

Bash中的模式匹配”glob patterns“和”extglob“