一、位置参数是命令行和脚本之间的桥梁

原笔记把位置参数列为 Shell 编程核心内容,最常见的是下面几个:

  • $1$2$3:脚本的第 1、2、3 个参数
  • $0:脚本本身的名字
  • $#:脚本参数个数
  • $@:所有参数
  • $*:所有参数

最基础的演示脚本如下:

#!/bin/bash
echo "脚本第1个参数:$1"
echo "脚本第2个参数:$2"
echo "脚本参数数量:$#"

执行效果:

bash /server/scripts/devops-shell/02.vars.sh a b c d e

二、位置参数最常见的使用场景

2.1 接收用户名并做检查

例如执行脚本时传入用户名,然后用 id 判断用户是否存在:

bash /server/scripts/devops-shell/03.check_user.sh zq

这种写法很适合做:

  • 用户检查
  • 服务管理脚本
  • 发布脚本参数传递
  • 巡检脚本指定主机或目录

2.2 输出脚本帮助

$0 非常适合用来打印使用方法:

#!/bin/bash
echo "Usage: $0 {start|stop|restart}"

2.3 检查参数个数

$# 最常与判断结合:

if [ $# -ne 1 ]; then
    echo "Help: $0 {start|stop|restart}"
    exit 6
fi

三、$10 为什么会出问题

这是原笔记专门强调的面试题:

  • $10 在 Shell 里会被解释成 $1 加上字符 0
  • 参数位数大于 9 时,应写成 ${10}${11}

例如:

echo $1 $2 $3 $4 $5 $10 $11
echo $1 $2 $3 $4 $5 ${10} ${11}

四、$@$* 的区别要看双引号

不加双引号时,$@$* 看起来差别不大;真正的差异出现在加上双引号之后。

set -- "I am oldboy" teacher lidao

for i in "$*"; do echo "$i"; done
for j in "$@"; do echo "$j"; done

结果是:

  • "$*" 会把所有参数合并成一个整体
  • "$@" 会保留每个参数的边界

因此,只要你打算在循环或函数中逐个处理参数,优先用 "$@"

五、状态变量用于判断命令有没有执行成功

原笔记列出的状态类特殊变量包括:

  • $?:上一个命令或脚本的返回值,0 表示成功
  • $$:当前脚本的 PID
  • $!:上一个后台命令的 PID
  • $_:上一个命令的最后一个参数

其中最常用的还是 $?

5.1 实战:输入命令并检查执行结果

#!/bin/bash
cmd="$@"
$cmd &>/dev/null

if [ $? -eq 0 ]; then
    echo "$cmd 运行成功"
else
    echo "$cmd 运行失败"
fi

调用示例:

bash /server/scripts/devops-shell/07.check_cmd.sh ls -a

六、参数展开让变量处理更高效

原笔记把参数展开分成几类:长度统计、删除、截取和替换。

6.1 统计长度

oldboy="lidao996"
echo ${#oldboy}

6.2 删除左边或右边的内容

var=oldboylidao996
echo ${var#oldboy}
echo ${var##*o}

dir=/etc/sysconfig/network-scripts/ifcfg-eth0
echo ${dir##*/}
echo ${dir%/*}

这个技巧很适合:

  • 提取文件名
  • 提取目录名
  • 删除固定前后缀

6.3 截取变量

var=oldboy
echo ${var:3}
echo ${var:1:3}

6.4 替换变量内容

var=oldboylidao996
echo ${var/o/-}
echo ${var//o/-}

七、实战:统计句子里长度不超过 6 的单词

原笔记给出的 Shell 版本示例如下:

#!/bin/bash
hua="I am oldboy teacher welcome to oldboy training class."
hua_del=$(echo "$hua" | sed 's#\.##g')

for word in ${hua_del}; do
    if [ ${#word} -le 6 ]; then
        echo "单词字符数小于等于6: ${word}"
    fi
done

同样的需求也可以用 awk 一条命令完成,这也说明参数展开和三剑客是可以互相替补的。

八、默认值扩展适合给变量兜底

原笔记列出的 4 种常见写法如下:

  • ${parameter:-word}:变量未定义或为空时,输出默认值,但不改变量本身
  • ${parameter:=word}:变量未定义或为空时,赋默认值并写回变量
  • ${parameter:?word}:变量未定义或为空时,输出错误信息
  • ${parameter:+word}:变量存在且非空时,用 word 替换输出

例如:

echo ${name:-root}
echo ${lidao:=996}
echo ${name:+root}

其中最常用的是 :-:=,非常适合给脚本参数、目录变量、服务名变量设置保底值。

九、变量赋值方式有哪些

原笔记把变量赋值分成 5 种常见来源:

  • 直接赋值:oldboy=lidao996
  • 命令结果赋值:hostname=$(hostname)
  • 脚本传参赋值:user_name=$1
  • read 交互赋值
  • 从文件读取内容赋值

其中 read 是交互脚本里必须掌握的。

9.1 read 常用参数

  • -p:提示信息
  • -t:超时退出
  • -s:不显示输入内容,适合密码输入

示例:

read -p "请输入2个数字num1 num2:" num1 num2
read -s -p "请输入密码:" pass

十、实战:用户输入密码后倒序输出

原笔记里的示例脚本如下:

#!/bin/bash
read -s -p "请输入密码:" pass
echo

pass_rev=$(echo "$pass" | rev)
echo "正在猜测你的密码....."
echo "马上破解成功"
echo "$pass_rev"
echo "看看对不对?"

这个例子虽然简单,但正好把 read -s、命令替换、变量输出串起来了。

十一、写参数处理脚本时的推荐顺序

把原笔记里的内容合起来,日常写脚本时可以遵循这个顺序:

1、用 $# 检查参数个数。 2、用 $0 输出帮助信息。 3、用 ${1:-default} 给参数设置默认值。 4、用 $? 检查关键命令是否成功。 5、用 read 补充必须的交互输入。

按这个套路写,脚本的参数入口会清晰很多,也更不容易因为输入异常而出错。