shell 特殊变量、特殊扩展变量、变量长度计算实践与应用

1. shell的特殊位置变量

$0 $n $# $* $$ $?

$0:获取当前执行shell脚本文件名,如果执行脚本包含路径,那么就包括脚本路径
$n:获取当前执行shell脚本的第n个参数值、n=1..9,当n为0时表示脚本文件名;如果n大于9,则用大括号括起来,例如${10},接的参数以空格隔开
$#:获取当前执行的shell脚本后面接的参数个数
$*:获取当前shell脚本所有传参的参数,不加引号和相同;如果给$*加上双引号,例如:“$*”,则表示将所有的参数视为单个字符串,相当于“$1 $2 $3”
:获取当前shell脚本所有传参的参数,不加引号和$*相同;如果给加上双引号,例如:“”,则表示将所有的参数视为独立字符串,相当于“$1“,”$2“,"$3"
$$:获取当前执行的Shell脚本的进程号
$?:上一条命令的退出码
  • $0 特殊变量的作用及变量实践,dirname,basename功能
[ scripts]# cat n.sh 
#!/bin/bash
echo $0
---------------------------------------------------------
## 若不带路径执行脚本,那么输出结果就是脚本的名字
[ scripts]# sh n.sh 
n.sh
## 若使用全路径执行脚本,那么输出结果就是全路径加上脚本的名字
[ scripts]# sh /server/scripts/n.sh 
/server/scripts/n.sh
`dirname 命令的作用是获取脚本的路径`
[ scripts]# dirname /server/scripts/n.sh 
/server/scripts
`basename 命令的作用是获取脚本的名称`
[ scripts]# basename /server/scripts/n.sh
n.sh
----------------------------------------------------------
[ scripts]# cat n.sh 
#!/bin/bash
echo $0
dirname $0
basename $0
  • $n实践
[ scripts]# cat q.sh 
echo $1 $2 $3 $4 $5 $6 $7 $8 $9    -- 设定接收的参数
echo $#								-- 打印输入参数个数
----------------------------------------------------------
[ scripts]# sh q.sh {a..z}
a b c d e f g h i					-- 接收的参数
26									-- 输入的参数个数
  • $#实践应用,脚本用法提示
[ scripts]# cat t2.sh 
#!/bin/bash
if [ $# -ne 2 ]
  then 
    echo "USAGE:/bin/sh $0 arg1 arg2"
    exit 1
fi
echo $1 $2
  • $?特殊变量功能实践
[ scripts]# cat test4.sh 
#!/bin/bash

[ $# -ne 2 ] && {
  echo "Must be two args."
  exit 119			--- 未正确执行时,echo $? 返回119
}
echo oldgirl		--- 正确执行时,echo $? 返回0

**在企业场景下,“\(? ”返回值的用法如下:**1)判断命令、脚本或函数等程序是否执行成功。2)若在脚本中调用执行“exit 数字”,则会返回这个数字给“\)? ”变量。
3)如果是在函数里,则通过“return 数字”把这个数字以函数返回值的形式传给“$? ”。

  • $$特殊变量功能及实践

$$就是获取当前执行的Shell脚本的进程号

[ scripts]# cat test_pid.sh 
#!/bin/bash
echo $$ > /tmp/a.pid
sleep 30  -- 休息30秒,模拟守护进程不退出
-------------------------------------------------------------------------------
[ scripts]# sh test_pid.sh &		-- 在后台运行脚本,&符号表示在后台运行
[1] 3802
[ scripts]# ps -ef|grep test_pid|grep -v grep
root       3802   2481  0 15:49 pts/1    00:00:00 sh test_pid.sh   -- 脚本进程
[ scripts]# cat /tmp/a.pid
3802		--- $$ 对应的值
  • 实现系统中多次执行某一个脚本后的进程只有一个(此为$$的企业级应用)

说明:有时执行定时任务脚本的频率比较快,并不知道上一个脚本是否真的执行完毕,但是,业务要求同一时刻只能有一个同样的脚本在运行,此时就可以利用$$获取上一次运行的脚本进程号,当程序重新运行时,根据获得的进程号,清理掉上一次的进程,运行新的脚本命令,脚本如下:

[ scripts]# cat pid.sh 
#!/bin/bash
pid_path=/tmp/a.pid
if [ -f "$pid_path" ]
  then
  	echo "kill `cat $pid_path` "
    kill `cat $pid_path`
    rm -rf $pid_path
fi
echo $$ >$pid_path
sleep 30
----------------------------------------------------------------------
[ scripts]# sh pid.sh &
[1] 5198
[ scripts]# sh pid.sh &   --- 多次执行同一个脚本
[2] 5205
[ scripts]# ps -ef|grep pid |grep -v grep
root       5205   2481  0 16:11 pts/1    00:00:00 sh pid.sh   -- 只有一个进程,执行前删除其余同名脚本进程
[1]-  Terminated              sh pid.sh

2. .和source与bash或sh执行脚本的区别

source sh bash ./

详解shell中source、sh、bash、./执行脚本的区别

  • 1、source命令用法
source FileName

作用:在当前bash环境下读取并执行FileName中的命令。该filename文件可以无"执行权限"
注:该命令通常用命令“.”来替代。
如:

source .bash_profile
. .bash_profile
两者等效

source(或点)命令通常用于重新执行刚修改的初始化文档。
source命令(从 C Shell 而来)是bash shell的内置命令。
点命令,就是个点符号,(从Bourne Shell而来)。

  • 2、sh和bash命令用法
sh FileName
 bash FileName

作用:在当前bash环境下读取并执行FileName中的命令。该filename文件可以无"执行权限"
注:两者在执行文件时的不同,是分别用自己的shell来跑文件。
sh使用“-n”选项进行shell脚本的语法检查,使用“-x”选项实现shell脚本逐条语句的跟踪,
可以巧妙地利用shell的内置变量增强“-x”选项的输出信息等。

  • 3、./的命令用法
./FileName

作用:打开一个子shell来读取并执行FileName中命令。
注:运行一个shell脚本时会启动另一个命令解释器.
每个shell脚本有效地运行在父shell(parent shell)的一个子进程里.
这个父shell是指在一个控制终端或在一个xterm窗口中给你命令指示符的进程.
shell脚本也可以启动他自已的子进程.
这些子shell(即子进程)使脚本并行地,有效率地地同时运行脚本内的多个子任务.

  • shell的嵌入命令:
: 空,永远返回为true
.   从当前shell中执行操作
break 退出for、while、until或case语句
cd 改变到当前目录
continue 执行循环的下一步
echo 反馈信息到标准输出
eval 读取参数,执行结果命令
exec 执行命令,但不在当前shell
exit 退出当前shell
export 导出变量,使当前shell可利用它
pwd 显示当前目录
read 从标准输入读取一行文本
readonly 使变量只读
return 退出函数并带有返回值
set 控制各种参数到标准输出的显示
shift 命令行参数向左偏移一个
test 评估条件表达式
times 显示shell运行过程的用户和系统时间
trap 当捕获信号时运行指定命令
ulimit 显示或设置shell资源
umask 显示或设置缺省文件创建模式
unset 从shell内存中删除变量或函数
wait 等待直到子进程运行完毕
  • 4、结论区别

下面再看下 shell 脚本各种执行方式(source./*.sh, . ./*.sh, ./*.sh)的区别

  • 结论一: ./*.sh的执行方式等价于sh ./*.sh或者bash ./*.sh,此三种执行脚本的方式都是重新启动一个子shell,在子shell中执行此脚本。

  • 结论二: source ./*.sh. ./*.sh的执行方式是等价的,即两种执行方式都是在当前shell进程中执行此脚本,而不是重新启动一个shell 而在子shell进程中执行此脚本。

  • 验证依据:没有被export导出的变量(即非环境变量)是不能被子shell继承的

  • 验证结果:

[ ~]#name=dangxu    //定义一般变量 
[ ~]# echo ${name} 
dangxu 
[ ~]# cat test.sh   //验证脚本,实例化标题中的./*.sh 
#!/bin/sh 
echo ${name} 
[ ~]# ls -l test.sh  //验证脚本可执行 
-rwxr-xr-x 1 root root 23 Feb 6 11:09 test.sh 
[ ~]# ./test.sh    //以下三个命令证明了结论一 
[ ~]# sh ./test.sh 
[ ~]# bash ./test.sh 
[ ~]# . ./test.sh   //以下两个命令证明了结论二 
dangxu 
[ ~]# source ./test.sh 
dangxu

3. Shell获取字符串长度

{#str} awk.length awk.NF wc-l wc-c expr.length

关于Shell获取字符串长度的多种方法,包括了利用\({#str}、利用awk的length方法、利用awk的NF项、利用wc的-L参数、利用expr的length方法以及利用expr的\)str : ".*"技巧来实现方法示例

  • 【方法一】:利用{#str} -- 推荐方法,也是最快的方法
:/home/fl# str="ABCDEF"
:/home/fl# echo ${#str}
6
  • 【方法二】:利用awk的length方法
:/home/fl# str="ABCDEF"
:/home/fl# echo ${str} | awk ‘{print length($str)}‘
6
:/home/fl# echo ${str} | awk ‘{print length}‘       ---可没有($str)
6
  • 【方法三】:利用awk的NF项
:/home/fl# str="ABCDEF"
:/home/fl# echo ${str} | awk -F "" ‘{print NF}‘
6

? 备注:-F为分隔符,NF为域的个数,即单行字符串的长度

  • 【方法四】:利用wc的-L参数
:/home/fl# str="ABCDEF"
:/home/fl# echo ${str} | wc -L
6
:/home/fl# cat /etc/passwd | wc -L
85

? 备注:
1)如果输入为单行字符串,输出为字符串的长度
2)如果输入为文件,则输出为文件中最长行的长度

  • 【方法五】:利用wc的-c参数
:/home/fl# echo -n "ABCDEF" | wc -L
6
:/home/fl# echo -n "ABCDEF" | wc -c
6
:/home/fl# echo "ABCDEF" | wc -c
7

备注:
-c参数:统计字符的个数
-n参数:去除字符串中的换行符

-L 参数:取行长,--max-line-length
-l 参数:取行数 ,--lines

  • 【方法六】:利用expr的length方法
:/home/fl# str="ABCDEF"
:/home/fl# expr length ${str}
6

3.1 截取变量指定内容

  • 截取OLDBOY变量的内容,从第2个字符之后开始截取,
  • 默认截取后面字符的全部,第2个字符不包含在内,也可理解为删除前面的多个字符
OLDBOY="hello world."
[ scripts]# echo ${OLDBOY:2}	-- 理解为:删除前面的2个字符
llo world.
[ scripts]# echo ${OLDBOY:2:5}   -- 理解为:删除前面的2个字符,再取5个字符
llo w
  • 有关匹配删除的小结:OLDBOY="abcABC123ABCabc"
- #表示从开头删除匹配最短。echo ${OLDBOY#a*c}  --注意:匹配只能以变量的首字母如`a`开始
  - ##表示从开头删除匹配最长。echo ${OLDBOY##a*c}  --注意:匹配只能以变量的首字母如`a`开始
  - %表示从结尾删除匹配最短。echo ${OLDBOY%a*c}  --注意:匹配只能以变量的首字母如`c`结束
  - %%表示从结尾删除匹配最长。echo ${OLDBOY%%a*c}  --注意:匹配只能以变量的首字母如`c`结束
  - `a*c`表示匹配的字符串,表示匹配所有,`a*c`匹配开头为a、中间为任意多个字符、结尾为c的字符串。
  - `a*C`表示匹配的字符串,表示匹配所有,`a*C`匹配开头为a、中间为任意多个字符、结尾为C的字符串。
  - 从头开始匹配时,匹配的首字母为变量首字母,否则即是不匹配。从尾开始的情况类推。
  • 使用oldgirl字符串代替变量$OLDBOY匹配的oldboy字符串。
[ scripts]# OLDBOY="I am oldboy,yes,oldboy"
[ scripts]# echo $OLDBOY
I am oldboy,yes,oldboy
[ scripts]# echo ${OLDBOY/oldboy/oldgirl}    -- 一个/表示只替换匹配的第一个
I am oldgirl,yes,oldboy
[ scripts]# echo ${OLDBOY//oldboy/oldgirl}	-- 两个//表示替换匹配的所有
I am oldgirl,yes,oldgirl
[ scripts]#
  • 利用变量的子串替换来实现修改文件名称
touch moox_10299_{1..10}_finished.jpg
## 文件名称中去掉_finished
f="moox_10299_1_finished.jpg" 
mv $f ${f//_finished/}  	 ---子串替换实现单个文件名称修改
for f in `ls *_finished*`	 ---子串替换循环实现批量文件名称修改
do
	mv $f ${f//_finished/}
done

3.2 Shell特殊扩展变量的实践:

  1. ${parameter:-word}功能实践
  2. ${parameter:=word}功能实践
  3. ${parameter:? word}功能实践
  4. ${parameter:+word}功能实践
  • 1.${parameter:-word}功能实践

\({parameter:-word}的作用:如果parameter变量值为空或未赋值,则会返回word字符串替代变量的值。结论:对于\){test:-UNSET},当test变量没值时,就返回变量结尾设置的UNSET字符串。

[ scripts]# result=${test:-UNSET}
[ scripts]# echo $result 
UNSET
[ scripts]# echo $test
--- 此处test为空值
[ scripts]# test=zhangfd
[ scripts]# result=${test:-UNSET}
[ scripts]# echo $result 
zhangfd
  • 2.${parameter:=word}功能实践

${parameter:=word}的作用是:如果parameter变量值为空或未赋值,就设置这个变量值为word,并返回其值。位置变量和特殊变量不适用。

当变量(result)值里的变量(test)值没有定义时,会给变量(result)赋值“:=”后面的内容,同时会把“:=”后面的内容赋值给变量(result)值里没有定义的变量(test)。这个变量的功能可以解决变量没有定义的问题,并确保没有定义的变量始终有值。

[ scripts]# unset result 
[ scripts]# unset test 
[ scripts]# result=${test:=UNSET}
[ scripts]# echo $result 
UNSET
[ scripts]# echo $test
UNSET   --- 此处test被赋值为UNSET,区别于 result=${test:-UNSET}
[ scripts]# test=zhangfd
[ scripts]# result=${test:-UNSET}
[ scripts]# echo $result 
zhangfd
  • 3.${parameter:? word}功能实践

{parameter:? word}的作用是:如果parameter变量值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值。

[ scripts]# unset result 
[ scripts]# unset test
[ scripts]# result=${test:?not define}
-bash: test: not define  --- 被定义为错误输出
[ scripts]# test=zhangfdsan
[ scripts]# result=${test:?not define}
[ scripts]# echo $result 
zhangfdsan
  • 4.${parameter:+word}功能实践

${parameter:+word}的作用是:如果parameter变量值为空或未赋值,则什么都不做,
否则word字符串将替代变量的值。

[ scripts]# unset result 
[ scripts]# unset test 
[ scripts]# result=${test:+defined}
[ scripts]# echo $result 
--- test没有定义,什么都不做
[ scripts]# test=zhangsan
[ scripts]# result=${test:+defined}	-- 注意要重新定义
[ scripts]# echo $result 
defined		--- 当test有赋值时,result被定义为+号后面的值
[ scripts]#