目录
$0
$1, $2 等等
$#
$* 与 "$*"
$@ 与 "$@"
$!
$_
$$
$PPID
$?
$BASH
$BASH_VERSION
$EUID 与 $UID
$GROUPS
$HOME
$HOSTNAME
$IFS
$PATH
$OLDPWD
$PWD
$PS1
$PS2
$PS4
$0
执行 Bash 脚本时,Bash 会自动将脚本的名称保存在内置变量 $0 中。因为 $0 基于的是实际的脚本文件名称,而不是在脚本中进行硬编码,所以在重命名脚本文件的名称后,不需要修改脚本的内容。比如下面的脚本片段:
#!/bin/bash ARGS=3 # 这个脚本需要 3 个参数. E_BADARGS=65 # 传递给脚本的参数个数不对. echo "Args number is : $#" echo $0 if [ $# -ne "$ARGS" ] # 测试脚本的参数个数。 then echo "Usage: $(basename $0) first-parameter second-parameter third-parameter" exit $E_BADARGS fi # 开始干正事儿
在上面的代码中我们使用了 $(basename $0) 的写法,这是因为 $0 会包含脚本文件的路径,为了让输出看起来清爽一些,我用 $(basename $0) 去掉了脚本的路径名称,下面是运行的结果:
$1, $2 等等
$0, $1,$2... 被称为位置参数。所谓的位置参数(positional parameter),指的是 Shell 脚本的命令行参数(argument);同时也表示在 Shell 函数内的函数参数。它们的名称是以单个的整数来命名。出于历史的原因,当这个整数大于 9 时,就应该以大括号{} 括起来。下面是一个简单的 demo:
#!/bin/bash echo $1 echo $2 echo $3
$#
位置参数的个数,具体的用法请参考 $0 中的示例。
$* 与 "$*"
所有的位置参数。但是 $* 与 "$*" 的表现是不一样的,我们通过下面的 demo 来介绍其异同。
$* 提供分隔后的参数:
for arg in $* do echo $arg done
$* 和 $@ 的表现是一样的。
"$*" 把所有参数看作一个字符串:
for arg in "$*" do echo $arg done
$@ 与 "$@"
所有的位置参数。$@ 和 $* 的表现是一样的。
"$@" 能够提供看上去比较合理的结果:
for arg in "$@" do echo $arg done
下面是 "$@" 的一个比较常见的用法如下:
if[ "$1"='node' ]; then SCRIPT_FILE= for ARG in "$@" do if[ "${ARG}"='main.js' ]; then SCRIPT_FILE='main.js' break fi done if[ -z "$SCRIPT_FILE" ]; then exec "$@""main.js" exit 0; fi fi exec"$@"
这是在常见 nodejs 的 docker 镜像时经常使用的一段代码:
"$@" 还常常与 shift 命令一起使用来丢弃参数 $1 #!/bin/bash # 使用./test.sh 1 2 3 4 5 来调用这个脚本 echo "$@" # 1 2 3 4 5 shift echo "$@" # 2 3 4 5 shift echo "$@" # 3 4 5 # 每次 "shift" 都会丢弃$1. # "$@" 将包含剩下的参数.
还可以使用 set 命令在脚本中设置位置参数:
#!/bin/bash set -- "First one" "second" "third:one" "" "Fifth: :one" # 设置这个脚本的参数, $1, $2, 等等. index=1 # 起始计数. echo "Listing args with \"\$@\":" for arg in "$@" do echo "Arg #$index = $arg" let "index+=1" done # $@ 把每个参数都看成是单独的单词. echo "Arg list seen as separate words."
$!
运行在后台的最后一个作业的 PID。
$ sleep 60 & [1] 6238 $ echo "$!" 6238
如果有多个在后台运行的任务,就需要通过 $! 来获得 PID 并进行 wait:
$ sleep 60 & $ pid1=$! $ sleep 100 & $ pid2=$! $ wait $pid1 # 等待第一个后台进程结束 $ wait $pid2 # 等待第二个后台进程结束
$_
这个变量保存之前执行的命令的最后一个参数的值。
把下面的代码保存在 test.sh 文件中:
#!/bin/bash echo $_ # ./test.sh du >/dev/null # 这么做命令行上将没有输出. echo $_ # du ls -al >/dev/null # 这么做命令行上将没有输出. echo $_ # -al (这是最后的参数) : echo $_ # :
下面是一个比较常见的用法,可以直接进入创建的目录:
$ mkdir hello && cd $_
$$
脚本自身的 PID (当前 bash 进程的 PID):
$PPID
进程的 $PPID 就是这个进程的父进程的 PID。
$?
$? 保存了最后所执行的命令的退出状态码,一般表示命令执行成功或失败。当函数返回之后,$? 保存函数中最后所执行的命令的退出状态码。这就是 bash 对函数 "返回值" 的处理方法。当一个脚本退出,$? 保存了脚本的退出状态码,这个退出状态码也就是脚本中最后一个执行命令的退出状态码。 0 表示成功,其它值表示错误。
当脚本以不带参数的 exit 命令来结束时,脚本的退出状态码就由脚本中最后执行的命令来决定(就是exit之前的命令)。不带参数的exit命令与 exit $? 的效果是一样的,甚至脚本的结尾不写 exit,也与前两者的效果相同。
我们还可以把 $? 保存到变量中,从而让脚本返回其中某个命令的返回值:
#!/bin/bash set -x go get -d -v golang.org/x/net/html go get -u github.com/jstemmer/go-junit-report go test -v 2>&1 > tmp status=$? $GOPATH/bin/go-junit-report < tmp > test_output.xml exit ${status}
上面的程序把 go test 命令的返回值保存到了变量 status 中,并通过 exit ${status} 作为脚本的返回值。
关于退出状态
在 Linux 系统中,程序(包括脚本)的退出状态是非常有用的,只要程序执行完成,就会向 Shell 返回一个退出状态码。这个状态码是一个数值,指明了程序是否成功结束。按照惯例,退出状态码为 0 表示程序运行成功;非 0 表示程序运行失败,不同的值对应着不同的失败原因。
造成程序运行失败的原因可能是非法参数,也可能是出现了错误的条件。比如 cp 命令,退出状态码 1 表示文件没有找到,2 表示文件不可读,3 表示目标目录没有找到,4 表示目标目录不可写,5 表示一般性错误。
$BASH
Bash 的二进制程序文件的路径:
$BASH_VERSION
检查系统上安装的 Bash 版本号:
检查 $BASH_VERSION 对于判断系统上到底运行的是哪个 shell 来说是一种非常好的方法。变量 $SHELL有时候不能够给出正确的答案。
$EUID 与 $UID
$EUID 表示 "有效" 用户 ID。
$UID 表示 用户ID号,是当前用户的用户标识号, 记录在 /etc/passwd 文件中。这是当前用户的真实 id, 即使只是通过使用 su 命令来临时改变为另一个用户标识, 这个 id 也不会被改变。$UID 是一个只读变量,不能在命令行或者脚本中修改它。
$GROUPS
当前用户所属的组。
这是一个当前用户的组 id 数组, 与记录在 /etc/passwd 文件中的内容一样:
$HOME
用户的 home 目录,一般是 /home/username。
$HOSTNAME
主机名称。
$IFS
内部域分隔符。这个变量用来决定 Bash 在解释字符串时如何识别域,或者单词边界。
$IFS默认为空白(空格, 制表符,和换行符),但这是可以修改的,比如在分析逗号分隔的数据文件时,就可以设置为逗号。注意 $* 使用的是保存在 $IFS 中的第一个字符来分隔位置参数的。
$IFS 处理其他字符与处理空白字符不同的 demo:
#!/bin/bash output_args_one_per_line() { for arg do echo "[$arg]" done } echo "IFS=\" \"" echo "-------" IFS=" " var=" a b c " output_args_one_per_line $var echo; echo "IFS=:" echo "-----" IFS=: var=":a::b:c:::" # 与上边一样, 但是用" "替换了":". output_args_one_per_line $var # 使用 : 后,冒号前后的空字符也被解析了。 exit 0
执行上面的脚本,结果如下:
$PATH
可执行文件的搜索路径。
当给出一个命令时,Bash 会自动生成一张哈希(hash)表,并且在这张哈希表中按照 PATH 变量中所列出的路径来搜索这个可执行命令。路径会存储在环境变量中,$PATH 变量本身就一个以冒号分隔的目录列表。通常情况下,系统都是在 /etc/profile 和 ~/.bashrc 中存储 $PATH 的定义,Ubuntu 是定义在 /etc/environment 文件中。
PATH=${PATH}:/opt/bin
将会把目录 /opt/bin 附加到当前目录列表中,在脚本中,这是一种把目录临时添加到 $PATH 中的权宜之计。当这个脚本退出时,$PATH 将会恢复以前的值(一个子进程,比如说一个脚本,是不能够修改父进程的环境变量的)。
当前的"工作目录",通常是不会出现在 $PATH 中的,这样做的目的是出于安全的考虑。因为当前目录是不断变化的,很有可能会存在与系统工具同名的恶意程序(比如你在网上下载了一个叫 cat 的恶意程序)。这时执行 cat 命令,就会运行当前目录下的 cat 恶意程序(把当前目录放在 PATH 变量的靠前位置的情况)。
还有一种情况,比如我们经常会自己用c语言或者其它的语言写一些程序,然后编译、链接为可执行文件。假如我们的可执行文件是做一些不可恢复性的操作,比如删除文件,格式化磁盘之类的。而这些文件名字又恰巧和我们系统 $PATH 下的某些常用可执行文件名字相同时,那么结果会出乎我们的意料。
也就是说当前目录是总在变化的,一会我们 cd 到这儿了,一会又 cd 到另一个地方去了。这样的话,当前目录下有哪些可执行文件也会随着改变的。有时候我们不会太在意自己处于的目录位置,如果当前目录在 $PATH中,那么我们也就不清楚自己干了什么。
而 $PATH 里面则放置了一些固定的目录,这些目录是不会变化的,这样的话,当我们输入命令时,永远可以保证不会随着自己的位置改变,而导致出乎意料。
$OLDPWD
前一个工作目录,可以通过下面的命令快速的回到前一个工作目录:
$ cd -
$PWD
工作目录(你当前所在的目录),这与内置命令 pwd 的作用相同:
下面的脚本演示了如何防止误删文件:
#!/bin/bash E_WRONG_DIRECTORY=73 clear # 清屏. TargetDirectory=/home/nick/testdir cd $TargetDirectory echo "Deleting stale files in $TargetDirectory." if [ "$PWD" != "$TargetDirectory" ] then # 防止偶然删错目录. echo "Wrong directory!" echo "In $PWD, rather than $TargetDirectory!" echo "Bailing out!" exit $E_WRONG_DIRECTORY fi rm -rf * # 删除文件 rm .[A-Za-z0-9]* # 删除点文件 echo "Done." echo "Old files deleted in $TargetDirectory." exit 0
执行上面的脚本,显示的结果如下:
$PS1
这是主提示符,可以在命令行中见到它,笔者的 Ubuntu16.04 中为:
看起来有些复杂,其实是添加了一些字体颜色的设置等内容。
$PS2
第二提示符,当你需要额外输入的时候,你就会看到它,默认值为 ">":
当我们往命令行上粘贴一个多行的命令时就会看到它的身影:
$PS4
第四提示符,当我们使用 -x 选项来调用脚本时,这个提示符会出现在每行输出的开头,默认为 "+":
运行下面的脚本:
set -x echo "Hello nick" echo 'This will show $PS4'
参考:
Bash Internal Variables
《高级 Bash 脚本编程指南》
《Unix/Linux/OS X 中的 Shell 编程》
来源:https://www.cnblogs.com/sparkdev/p/9934595.html