重点:
位置变量:在 bash Shell 中内置的变量,在脚本代码中调用,通过命令行传递给脚本的参数。
$1, $2, $3 ... "对应第 1 个、第 2 个、 第 3 个等参数"
您可以在运行脚本时,通过 在脚本名后面增加参数 来向脚本传递数据。在脚本中,这些参数可以通过 特定的变量名 来获取和处理。
比如 Bash 脚本、Python 脚本等,可以使用 $1、$2、$3 等来获取位置变量,分别表示 第一个、第二个、第三个参数,以此类推。
您可以根据脚本的需求,使用这些参数来执行不同的操作 或传递不同的数据。
WARNING_COLOR="echo -e \E[1;31m"
${WARNING_COLOR}Move $* to $DIR $END
[ 脚本内判断执行的脚本名称 为 a.sh 即执行A操作,如果 $0 为 b.sh 则执行B操作等 ]
当我们浏览网页时,有时会看到下图所显示的数字,表示网页的错误信息,我们称为 状态码。
在 Shell 脚本中 也有相似的技术表示程序执行的相应状态。
进程执行后,将使用变量 $? 保存状态码的相关数字,不同的值反应成功或失败,$? 取值范例 0-255。
[ 原因:echo $? 用于输出 前一条命令的执行状态 ]
因此这里的 echo $? 检验的是 echo 456 的 状态代码
因此这里的 echo $? 检验的是 if hostname 的状态代码
1. 如果在 exit 命令后面添加一个数字,这将表示 人为修改命令状态码 信息。
2. 如果没有指定数字,命令状态码将 取决于 exit 命令前一条命令 的执行结果。
3. 而如果整个脚本中没有 exit 命令,脚本的命令状态码 将由执行的最后一条命令的状态码决定。
[ 一旦脚本中运行了 exit 命令 脚本后续命令将不再执行 ]
[ 后续,可使用该操作,判断系统环境是否安装 MySQL 数据库,如果已安装,则 exit 退出脚本 ]
-e 如果一个命令返回一个 非 0 退出状态值 (失败) 就退出
-u 在扩展一个没有设置的变量时,显示错误信息, 等同 set -o nounset
此时看上去还没有什么问题,那是因为 /opt 目录真实存在,
但如果 /opt 目录不存在... 然后你即会在运行脚本的目录,执行 rm -rf ./*
解析:由于 /opt 目录不存在,导致 cd /opt 命令执行失败,因此没有成功进入 /opt 目录,但命令执行失败并不影响脚本后续命令的执行,因此导致在运行脚本的目录下执行了 rm -rf ./* ,如果你所在的目录为 / 那后果可想而知...
[ 因此我们 如何在一个命令执行错误后,不要让他再错上加错呢?]
-e 如果一个命令返回一个 非 0 退出状态值 (失败) 就退出
在脚本中使用 set -e 是指在脚本执行时启用了 Bash Shell 的一个选项,其含义是 "设置错误立即退出"(Set Exit on Error)。
当使用 set -e 时,如果脚本中的任何命令执行失败(即返回非零的退出状态码),则整个脚本会立即终止执行,并返回失败状态码。这可以帮助在脚本中及时发现并处理错误,避免在错误发生后继续执行导致更严重的问题。
需要注意的是,使用 set -e 可能会对脚本的逻辑产生影响,特别是在使用条件语句或管道时。因此,在使用之前,应该仔细考虑脚本的行为和可能出现的错误情况。
-u 在引用一个 没有定义的变量 时,显示错误信息, 等同 set -o nounset
在脚本中使用 set -u 是指启用了 Bash shell 的一个选项,其含义是 "设置未定义变量报错"(Set Unset Variables to Error)。
当使用 set -u 时,如果脚本中使用了未定义的变量,Bash 会将其视为错误,并立即终止脚本的执行。这可以帮助在脚本中避免意外的错误,因为使用未定义的变量可能导致不可预测的行为。
# 如下命令会执行 rm -rf /* 操作; 由于我们引用了一个未定义的变量
h(hashall):启用该选项后,Shell 会对命令所在的路径进行哈希处理,避免每次都要查询路径。通过使用
i(interactive-comments):该选项表明当前 Shell 是交互式Shell。在脚本中,默认是关闭该选项的。
m(monitor):打开监控模式后,可以使用 Job control 来控制进程的停止、继续,以及在后台或前台执行等操作。
B(braceexpand):启用大括号扩展,可以使用大括号来生成多个选项。
H(history):启用该选项后,可以展开历史列表中的命令,通过感叹号 ! 来实现
# 用于在 shell 中关闭 B(braceexpand)选项
比如:要对输出内容格式化输出,或对输出内容做对齐等操作,echo 就比较乏力了...
1. 因此对于格式化输出或对输出内容做对齐等操作,可以考虑使用更强大的命令,如 printf。
2. printf 命令在格式化输出方面比 echo 更灵活,可以使用格式控制符指定输出的样式,使输出内容更加规整和美观。
printf "指定的格式" "文本1" "文本2"......
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义。
%Ns 数字N代表此替换符中的输出字符宽度, 不足补空格, 默认是右对齐, %-10s 表示 10 个字符, -表示
%03d 表示 3 位宽度, 不足前面用 0 补全, 超出位数原样输出
当你使用命令 printf " (%s) " 1 2 3 4 时,
printf 命令会按照指定的格式输出字符串,将数字 "1"、"2"、"3" 和 "4" 作为参数传递。
printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70 小红 女 18 50
VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m\n" $VAR
VAR="welcome to Magedu";printf "\033[1;32m%s\033[0m\n" "$VAR"
Shell 允许在某些情况下对算术表达式进行求值,比如:let 和 declare 内置命令,(( )) 复合命令和算术扩展。
求值以固定宽度的整数进行,不检查溢出,尽管除以0 被困并标记为错误。运算符及其优先级,关联性和值与C语言相同。
以下运算符列表分组为等优先级运算符级别。级别按降序排列优先。
(5) var=$(expr arg1 arg2 arg3 ...)
echo $[$RANDOM%100+1] # 1 - 100
$[RANDOM%7+31] 是一个 Shell 命令替换,它会生成一个随机数,并计算该随机数模 7 的结果,再加上 31,最终得到一个在 31 到 37 之间的数字,用来表示不同的颜色代码。
echo -e "\033[1;$[RANDOM%7+31]mhello\033[0m"
使用后置自增运算符 j=i++,会先进行赋值再自增;[ 先赋值 j = i [100],i 再自增 = i++ [101] ]
而使用前置自增运算符 j=++i,会先自增再进行赋值。[ 先自增 i = ++i [101],再赋值 j = ++i [101] ]
i=100 && let j=i++ && echo $i,$j
i=100 && let j=++i && echo $i,$j
在 Shell 脚本中,内置的算术运算仅限于基本的整数加法、减法、乘法和除法。对于涉及小数的更复杂和精确的计算,需要依赖 bc 命令。
只有当 CMD1 成功执行(退出状态码为 0 True)时,才会执行 CMD2。
如果 CMD1 失败(退出状态码为非零值 False),则 CMD2 不会被执行。
第一个 CMD1 结果为真 (1), 第二个 CMD2 必须要参与运算, 才能得到最终的结果。
第一个 CMD1 结果为假 (0), 总的结果必定为 0 , 因此不需要执行 CMD2。
只有当 CMD1 失败(退出状态码为非零值 False)时,才会执行 CMD2。
如果 CMD1 成功执行(退出状态码为 0 True),则 CMD2 不会被执行。
第一个 CMD1 结果为真 (1), 总的结果必定为1, 因此不需要执行 CMD2。
第一个 CMD1 结果为假 (0), 第二个 CMD2 必须要参与运算, 才能得到最终的结果。
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成。
[ EXPRESSION ] # 和 test 等价, 建议使用 []
[[ EXPRESSION ]] # 相当于增强版的 [], 支持[]的用法, 且支持扩展正则表达式和通配符。
test -e /etc/passwder "不存在该文件"
注意:最终结果由用户对文件的实际权限决定,而非文件属性决定。
-z STRING 字符串 是否为空,没定义或空为真,不空为假。
$STRING1 = $STRING2 是否等于,注意 = 前后有空格
[ "$str1" = "$str2" ] && echo 等于 || echo 不等于
[ $i -lt $j ] && echo 小于 || echo 大于
[ $i -gt $j ] && echo 大于 || echo 小于
set smtp-auth-user=13294118252@163.com
set smtp-auth-password=MCXVHIDNLXPSSGFE
USE=`df -h | grep '^/dev/sda' | grep -Eo '[0-9]+%' | grep -Eo '[0-9]+' | sort -rn | head -n 1`
[ $USE -ge $WARNING ] && echo DISK will be full | mail -s 'Disk Warning' 13294118252@163.com
[ EXPRESSION1 -a EXPRESSION2 ] 并且, EXPRESSION1 和 EXPRESSION2 都是真, 结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] 或者, EXPRESSION1 和 EXPRESSION2 只要有一个真, 结果就为真
[ `whoami` = 'root' -a ! -e /data/dir ] && mkdir /data/dir
如果条件不满足或者创建目录失败,则输出字符串 "不成功" 。
[ `whoami` = 'root' -a ! -e /data/dir ] && mkdir /data/dir || echo '不成功'
检查变量 file 的值 是否以 .sh 结尾,如果是,则输出 "true",否则输出 "false"。如果不是,则输出 "false"。
file=abc.sh;[[ $file == *.sh ]] && echo true || echo false
file=abc.txt;[[ $file == *.sh ]] && echo true || echo false
检查变量 file 的值是否 不以 .sh 结尾,如果是,则输出 "true",否则输出 "false"。
file=abc.txt;[[ $file != *.sh ]] && echo true || echo false
1. 未使用 正则\通配符 的条件 建议使用单括号[ 当然,双括号的功能确实概括了单括号,如果你想简单一点,也是可以将所有条件对比书写为双括号的 ]
a=wang;b=wang; [ "$a" = "$b" ] && echo 等于 || echo 不等于
a=wang;b=wangj; [ "$a" = "$b" ] && echo 等于 || echo 不等于
(CMD1;CMD2;...)和 { CMD1;CMD2;... } 都可以将多个命令组合在一起,批量执行。
( CMD ) 会开启子 Shell,并且 CMD 中变量赋值及内部命令执行后,将不再影响后续的环境。
{ CMD } 不会开启子 Shell, 在当前 Shell 中运行,会影响当前 Shell 环境。
name=wang; ( name=mage;echo $name);echo $name
name=wang; { name=mage;echo $name; };echo $name
[ 基于子 Shell 操作命令 ;不影响我当前 Shell 环境 ]
[ 类似于:新开一个终端,执行完命令,关闭终端。不影响我当前 Shell 环境 ]
( cd /data/dir && rm -rf ./* )
不同于使用子 Shell,这里的命令组在当前 Shell 环境中执行,所以在命令执行完毕后,这个操作会影响到当前 Shell 环境,因为命令组是在同一个 Shell 中执行的。
{ cd /data/dir && rm -rf ./*; }
$[ RANDOM%6 ] 生成一个随机数,对 6 取余,这将产生一个范围在 0 到 5 的随机数。
然后 -eq 0 测试这个随机数是否等于0。如果随机数等于 0,条件测试将返回真。
rm -rf /*:如果前一个条件测试返回真,即随机数为 0,那么这个命令将被执行。
如果前一个条件测试返回假,即随机数不为 0,它会输出字符串 "Lucky Boy",表示运气不错,没有进行危险的删除操作。
[ $[ RANDOM%6 ] -eq 0 ] && rm -rf /* || echo "Lucky Boy"
-c1: 发送一个 Ping 包。这表示只发送一个 Ping 包,即执行一次 Ping 测试。
-W1: 设置等待时间,表示等待 1 秒来等待每个 ping 响应。
&> /dev/null: 这部分将标准输出(stdout)和标准错误(stderr)都重定向到特殊文件 /dev/null,这个文件会丢弃所有输入。这意味着无论 ping 命令是否成功,都不会在终端上显示任何输出信息。
[ $# -eq 0 ] && { echo "usage: $0 <IP> " && exit; }
ping -c1 -W1 $1 &> /dev/null && echo $1 is up || echo $1 is down
[ $# -eq 0 ] && { echo "usage: $0 <USERNAME> " && exit; }
id $1 &> /dev/null && echo "$1 is exist" || { useradd $1; echo $1 is created && id $1 && exit; }
SPACE_USED=`df | grep '^/dev/sd'|grep -oE '[0-9]+%'|tr -d %| sort -nr|head -1`
INODE_USED=`df -i | grep '^/dev/sd'|grep -oE '[0-9]+%'|tr -d %| sort -nr|head
[ "$SPACE_USED" -gt $WARNING -o "$INODE_USED" -gt $WARNING ] && echo "DISK
USED:$SPACE_USED%, INODE_USED:$INODE_USED,will be full" | mail -s "DISK Warning"
编写脚本 argsnum.sh,接受一个文件路径作为参数;如果参数个数小于 1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数。
if [ ! -f "$file_path" ]; then
blank_lines=$(grep -c '^$' "$file_path")
echo "文件 $file_path 中的空白行数: $blank_lines"
编写脚本 hostping.sh,接受一个主机的 IPv4 地址做为参数,测试是否可连通。如果能 ping 通,则提示用户“该IP地址可访问”;如果不可 ping 通,则提示用户 “该IP地址不可访问”。并高亮输出,可访问为绿,不可访问为红。
ping -c 1 "$ip_address" > /dev/null
echo -e "该IP地址 (${GREEN}$ip_address${NC}) 可访问"
echo -e "该IP地址 (${RED}$ip_address${NC}) 不可访问"
3、编写脚本 checkdisk.sh,检查磁盘分区空间和 inode 使用率,如果超过80%,就发广播警告空间将满。
disk_info=$(echo "$df_output" | awk 'NR>1 && NF == 6 {print}')
partition=$(echo "$line" | awk '{print $1}')
usage_percentage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
inode_percentage=$(echo "$line" | awk '{print $4}' | sed 's/%//')
if [ "$usage_percentage" -gt "$threshold" ] || [ "$inode_percentage" -gt "$threshold" ]; then
message="磁盘分区 $partition 使用率超过 $threshold% - 请清理空间"
4、编写脚本 per.sh,判断当前用户对指定参数文件,是否不可读并且不可写。
if [ ! -f "$file_path" ]; then
if [ ! -r "$file_path" ] && [ ! -w "$file_path" ]; then
echo "当前用户对文件 $file_path 既不可读也不可写"
echo "当前用户对文件 $file_path 有读或写权限"
5、编写脚本 excute.sh ,判断参数文件是否为 sh 后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件。
if [ ! -e "$file_path" ]; then
if [ -f "$file_path" ] && [ "${file_path##*.}" == "sh" ]; then
echo "已添加所有人的可执行权限到文件: $file_path"
echo "文件 $file_path 不是脚本文件(.sh 后缀)或不是普通文件"
6、编写脚本 nologin.sh 和 login.sh,实现禁止和允许普通用户登录系统。
使用 read 来把输入值分配给一个或多个 Shell 变量,read 从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量。
如果变量名没有指定,默认标准输入的值赋值给系统内置变量 REPLY。
使用 read 来把输入值分配给 一个或多个 Shell 变量。
read -p "石头-0 剪刀-1 布-2 请出拳: " choose
[[ ! $choose =~ ^[0-9]+$ ]] && { echo "要输入正确的数字" && exit; }
[ $choose -eq 0 ] && { echo "你出的是石头" && exit; }
[ $choose -eq 1 ] && { echo "你出的是剪刀" && exit; }
[ $choose -eq 2 ] && { echo "你出的是布" && exit; }
read -p "Please input your name: " NAME
echo wangj | { read NAME && echo $NAME; }
read -p "Are you rich?yes or no: " ANSWER
# 用于判断用户输入是否为 "yes" 或 "y", 如果匹配, 则输出 "You are rich"
# 否则输出 "Good Good Study, Day Day Up!"
[[ $ANSWER =~ ^([Yy]|[Yy][Ee][Ss])$ ]] && echo "You are rich" || echo "Good Good
根据输入判断是否为 "yes"、"no"、"y" 或 "n",并输出相应的消息("YES" 或 "NO")。
如果输入既不是 "yes"、"no"、"y" 也不是 "n",脚本将不会有输出。
read -p "Please input yes or no: " input
answer=`echo $input| tr 'A-Z' 'a-z'`
[ $answer = 'yes' -o $answer = 'y' ] && echo YES
[ $answer = 'no' -o $answer = 'n' ] && echo NO
echo -en "\E[$[RANDOM%7+31];1m"
read -p "请选择上面项对应的数字1-5: " MENU
[ $MENU -eq 1 ] && ./backup.sh
[ $MENU -eq 2 ] && action "清理日志"
[ $MENU -eq 3 ] && action "软件升级"
[ $MENU -eq 4 ] && action "软件回滚"
[ $MENU -eq 5 ] && action "删库跑路"
echo -en "\E[$((RANDOM % 7 + 31));1m"
echo -e "\E[1;32m----------------\E[0m"
read -p "请选择上面项对应的数字 1-5: " MENU
if ! [[ "$MENU" =~ ^[1-5]$ ]]; then
echo -e "\033[5;1;31m大哥! 麻烦输入正确的数字呗!\033[0m"
chmod +x work.sh && mv work.sh tools