运维管理30 分钟阅读
一篇文章让你彻底掌握 Shell
大家好,这里是 Lucifer三思而后行,专注于提升数据库运维效率。 @TOC Bash 是 Linux 标准默认的 shell 解释器,可以说 bash 是 shell 编程的基础。 本文主要介绍 bash 的语法,对于 Linux 指令不做任何介绍。 bash ███████╗██╗ ██╗███████╗██╗ ██╗ ██╔════╝██║ ██║██╔════╝██║
2024年8月9日阅读—点赞—收藏—
database
在知识库中专注阅读,并随时返回相关工具与课程
大家好,这里是 Lucifer三思而后行,专注于提升数据库运维效率。 @TOC Bash 是 Linux 标准默认的 shell 解释器,可以说 bash 是 shell 编程的基础。 本文主要介绍 bash 的语法,对于 Linux 指令不做任何介绍。 bash ███████╗██╗ ██╗███████╗██╗ ██╗ ██╔════╝██║ ██║██╔════╝██║
大家好,这里是 Lucifer三思而后行,专注于提升数据库运维效率。
本章目标:掌握本章核心知识点 前置要求:完成前序章节学习 预计时长:60 分钟
Bash 是 Linux 标准默认的 shell 解释器,可以说 bash 是 shell 编程的基础。
本文主要介绍 bash 的语法,对于 Linux 指令不做任何介绍。
1███████╗██╗ ██╗███████╗██╗ ██╗2██╔════╝██║ ██║██╔════╝██║ ██║3███████╗███████║█████╗ ██║ ██║4╚════██║██╔══██║██╔══╝ ██║ ██║5███████║██║ ██║███████╗███████╗███████╗6╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 Shell。
Shell 脚本(shell script),是一种为 shell 编写的脚本程序,一般文件后缀为 .sh。
业界所说的 shell 通常都是指 shell 脚本,但 shell 和 shell script 是两个不同的概念。
Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Shell 的解释器种类众多,常见的有:
在 shell 脚本,#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器。#! 被称作 shebang(也称为 Hashbang )。
所以,你应该会在 shell 中,见到诸如以下的注释:
指定 sh 解释器:
1#!/bin/sh指定 bash 解释器:
1#!/bin/bash注意: 上面的指定解释器的方式是比较常见的,但有时候,你可能也会看到下面的方式:
1#!/usr/bin/env bash这样做的好处是,系统会自动在 PATH 环境变量中查找你指定的程序(本例中的 bash)。相比第一种写法,你应该尽量用这种写法,因为程序的路径是不确定的。这样写还有一个好处,操作系统的 PATH 变量有可能被配置为指向程序的另一个版本。比如,安装完新版本的 bash,我们可能将其路径添加到 PATH 中,来“隐藏”老版本。如果直接用 #!/bin/bash,那么系统会选择老版本的 bash 来执行脚本,如果用 #!/usr/bin/env bash,则会使用新版本。
shell 有交互和非交互两种模式。
简单来说,你可以将 shell 的交互模式理解为执行命令行。
看到形如下面的东西,说明 shell 处于交互模式下:
1user@host:~$接着,便可以输入一系列 Linux 命令,比如 ls,grep,cd,mkdir,rm 等等。
简单来说,你可以将 shell 的非交互模式理解为执行 shell 脚本。
在非交互模式下,shell 从文件或者管道中读取命令并执行。
当 shell 解释器执行完文件中的最后一个命令,shell 进程终止,并回到父进程。
可以使用下面的命令让 shell 以非交互模式运行:
1sh /path/to/script.sh2bash /path/to/script.sh3source /path/to/script.sh4./path/to/script.sh上面的例子中,script.sh 是一个包含 shell 解释器可以识别并执行的命令的普通文本文件,sh 和 bash 是 shell 解释器程序。你可以使用任何喜欢的编辑器创建 script.sh(vim,nano,Sublime Text, Atom 等等)。
其中,source /path/to/script.sh 和 ./path/to/script.sh 是等价的。
除此之外,你还可以通过 chmod 命令给文件添加可执行的权限,来直接执行脚本文件:
1chmod +x /path/to/script.sh #使脚本具有执行权限2/path/to/test.sh这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如:
💻 “示例源码”:
1#!/usr/bin/env bash2echo "Hello, world!"上面的例子中,我们使用了一个很有用的命令 echo 来输出字符串到屏幕上。
前面虽然两次提到了 #! ,但是本着重要的事情说三遍的精神,这里再强调一遍:
在 shell 脚本,#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器。#! 被称作 shebang(也称为 Hashbang )。
#! 决定了脚本可以像一个独立的可执行文件一样执行,而不用在终端之前输入 sh, bash, python, php 等。
1# 以下两种方式都可以指定 shell 解释器为 bash,第二种方式更好2#!/bin/bash3#!/usr/bin/env bash注释可以说明你的代码是什么作用,以及为什么这样写。
shell 语法中,注释是特殊的语句,会被 shell 解释器忽略。
# 开头,到行尾结束。:<<EOF 开头,到 EOF 结束。💻 “示例源码”:
1#--------------------------------------------2# shell 注释示例3# author:zp4#--------------------------------------------56# echo '这是单行注释'78########## 这是分割线 ##########910:<<EOF11echo '这是多行注释'12echo '这是多行注释'13echo '这是多行注释'14EOFecho 用于字符串的输出。
输出普通字符串:
1echo "hello, world"2# Output: hello, world输出含变量的字符串:
1echo "hello, \"zp\""2# Output: hello, "zp"输出含变量的字符串:
1name=zp2echo "hello, \"${name}\""3# Output: hello, "zp"输出含换行符的字符串:
1# 输出含换行符的字符串2echo "YES\nNO"3# Output: YES\nNO45echo -e "YES\nNO" # -e 开启转义6# Output:7# YES8# NO输出含不换行符的字符串:
1echo "YES"2echo "NO"3# Output:4# YES5# NO67echo -e "YES\c" # -e 开启转义 \c 不换行8echo "NO"9# Output:10# YESNO输出重定向至文件:
1echo "test" > test.txt输出执行结果:
1echo `pwd`2# Output:(当前目录路径)💻 “示例源码”:
1#!/usr/bin/env bash23# 输出普通字符串4echo "hello, world"5# Output: hello, world67# 输出含变量的字符串8echo "hello, \"zp\""9# Output: hello, "zp"1011# 输出含变量的字符串12name=zp13echo "hello, \"${name}\""14# Output: hello, "zp"1516# 输出含换行符的字符串17echo "YES\nNO"18# Output: YES\nNO19echo -e "YES\nNO" # -e 开启转义20# Output:21# YES22# NO2324# 输出含不换行符的字符串25echo "YES"26echo "NO"27# Output:28# YES29# NO3031echo -e "YES\c" # -e 开启转义 \c 不换行32echo "NO"33# Output:34# YESNO3536# 输出内容定向至文件37echo "test" > test.txt3839# 输出执行结果40echo `pwd`41# Output:(当前目录路径)printf 用于格式化输出字符串。
默认,printf 不会像 echo 一样自动添加换行符,如果需要换行可以手动添加 \n。
💻 “示例源码”:
1# 单引号2printf '%d %s\n' 1 "abc"3# Output:1 abc45# 双引号6printf "%d %s\n" 1 "abc"7# Output:1 abc89# 无引号10printf %s abcdef11# Output: abcdef(并不会换行)1213# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出14printf "%s\n" abc def15# Output:16# abc17# def1819printf "%s %s %s\n" a b c d e f g h i j20# Output:21# a b c22# d e f23# g h i24# j2526# 如果没有参数,那么 %s 用 NULL 代替,%d 用 0 代替27printf "%s and %d \n"28# Output:29# and 03031# 格式化输出32printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg33printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.123434printf "%-10s %-8s %-4.2f\n" 杨过 男 48.654335printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.987636# Output:37# 姓名 性别 体重kg38# 郭靖 男 66.1239# 杨过 男 48.6540# 郭芙 女 47.99跟许多程序设计语言一样,你可以在 bash 中创建变量。
Bash 中没有数据类型,bash 中的变量可以保存一个数字、一个字符、一个字符串等等。同时无需提前声明变量,给变量赋值会直接创建变量。
访问变量的语法形式为:${var} 和 $var 。
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。
1word="hello"2echo ${word}3# Output: hello使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
1rword="hello"2echo ${rword}3readonly rword4# rword="bye" # 如果放开注释,执行时会报错使用 unset 命令可以删除变量。变量被删除后不能再次使用。unset 命令不能删除只读变量。
1dword="hello" # 声明变量2echo ${dword} # 输出变量值3# Output: hello45unset dword # 删除变量6echo ${dword}7# Output: (空)常见的环境变量:
这里 有一张更全面的 Bash 环境变量列表。
💻 “示例源码”:
1#!/usr/bin/env bash23################### 声明变量 ###################4name="world"5echo "hello ${name}"6# Output: hello world78################### 输出变量 ###################9folder=$(pwd)10echo "current path: ${folder}"1112################### 只读变量 ###################13rword="hello"14echo ${rword}15# Output: hello16readonly rword17# rword="bye" # 如果放开注释,执行时会报错1819################### 删除变量 ###################20dword="hello" # 声明变量21echo ${dword} # 输出变量值22# Output: hello2324unset dword # 删除变量25echo ${dword}26# Output: (空)2728################### 系统变量 ###################29echo "UID:$UID"30echo LOGNAME:$LOGNAME31echo User:$USER32echo HOME:$HOME33echo PATH:$PATH34echo HOSTNAME:$HOSTNAME35echo SHELL:$SHELL36echo LANG:$LANG3738################### 自定义变量 ###################39days=1040user="admin"41echo "$user logged in $days days age"42days=543user="root"44echo "$user logged in $days days age"45# Output:46# admin logged in 10 days age47# root logged in 5 days age4849################### 从变量读取列表 ###################50colors="Red Yellow Blue"51colors=$colors" White Black"5253for color in $colors54do55 echo " $color"56doneshell 字符串可以用单引号 '',也可以用双引号 “”,也可以不用引号。
综上,推荐使用双引号。
1# 使用单引号拼接2name1='white'3str1='hello, '${name1}''4str2='hello, ${name1}'5echo ${str1}_${str2}6# Output:7# hello, white_hello, ${name1}89# 使用双引号拼接10name2="black"11str3="hello, "${name2}""12str4="hello, ${name2}"13echo ${str3}_${str4}14# Output:15# hello, black_hello, black1text="12345"2echo ${#text}3# Output:4# 51text="12345"2echo ${text:2:2}3# Output:4# 34从第 3 个字符开始,截取 2 个字符。
1#!/usr/bin/env bash23text="hello"4echo `expr index "${text}" ll`56# Execute: ./str-demo5.sh7# Output:8# 3查找 ll 子字符在 hello 字符串中的起始位置。
💻 “示例源码”:
1#!/usr/bin/env bash23################### 使用单引号拼接字符串 ###################4name1='white'5str1='hello, '${name1}''6str2='hello, ${name1}'7echo ${str1}_${str2}8# Output:9# hello, white_hello, ${name1}1011################### 使用双引号拼接字符串 ###################12name2="black"13str3="hello, "${name2}""14str4="hello, ${name2}"15echo ${str3}_${str4}16# Output:17# hello, black_hello, black1819################### 获取字符串长度 ###################20text="12345"21echo "${text} length is: ${#text}"22# Output:23# 12345 length is: 52425# 获取子字符串26text="12345"27echo ${text:2:2}28# Output:29# 343031################### 查找子字符串 ###################32text="hello"33echo `expr index "${text}" ll`34# Output:35# 33637################### 判断字符串中是否包含子字符串 ###################38result=$(echo "${str}" | grep "feature/")39if [[ "$result" != "" ]]; then40 echo "feature/ 是 ${str} 的子字符串"41else42 echo "feature/ 不是 ${str} 的子字符串"43fi4445################### 截取关键字左边内容 ###################46full_branch="feature/1.0.0"47branch=`echo ${full_branch#feature/}`48echo "branch is ${branch}"4950################### 截取关键字右边内容 ###################51full_version="0.0.1-SNAPSHOT"52version=`echo ${full_version%-SNAPSHOT}`53echo "version is ${version}"5455################### 字符串分割成数组 ###################56str="0.0.0.1"57OLD_IFS="$IFS"58IFS="."59array=( ${str} )60IFS="$OLD_IFS"61size=${#array[*]}62lastIndex=`expr ${size} - 1`63echo "数组长度:${size}"64echo "最后一个数组元素:${array[${lastIndex}]}"65for item in ${array[@]}66do67 echo "$item"68done6970################### 判断字符串是否为空 ###################71#-n 判断长度是否非零72#-z 判断长度是否为零7374str=testing75str2=''76if [[ -n "$str" ]]77then78 echo "The string $str is not empty"79else80 echo "The string $str is empty"81fi8283if [[ -n "$str2" ]]84then85 echo "The string $str2 is not empty"86else87 echo "The string $str2 is empty"88fi8990# Output:91# The string testing is not empty92# The string is empty9394################### 字符串比较 ###################95str=hello96str2=world97if [[ $str = "hello" ]]; then98 echo "str equals hello"99else100 echo "str not equals hello"101fi102103if [[ $str2 = "hello" ]]; then104 echo "str2 equals hello"105else106 echo "str2 not equals hello"107fibash 只支持一维数组。
数组下标从 0 开始,下标可以是整数或算术表达式,其值应大于或等于 0。
1# 创建数组的不同方式2nums=([2]=2 [0]=0 [1]=1)3colors=(red yellow "dark blue")1echo ${nums[1]}2# Output: 11echo ${colors[*]}2# Output: red yellow dark blue34echo ${colors[@]}5# Output: red yellow dark blue上面两行有很重要(也很微妙)的区别:
为了将数组中每个元素单独一行输出,我们用 printf 命令:
1printf "+ %s\n" ${colors[*]}2# Output:3# + red4# + yellow5# + dark6# + blue为什么 dark 和 blue 各占了一行?尝试用引号包起来:
1printf "+ %s\n" "${colors[*]}"2# Output:3# + red yellow dark blue现在所有的元素都在一行输出 —— 这不是我们想要的!让我们试试 ${colors[@]}
1printf "+ %s\n" "${colors[@]}"2# Output:3# + red4# + yellow5# + dark blue在引号内,${colors[@]}将数组中的每个元素扩展为一个单独的参数;数组元素中的空格得以保留。
1echo ${nums[@]:0:2}2# Output:3# 0 1在上面的例子中,${array[@]} 扩展为整个数组,:0:2 取出了数组中从 0 开始,长度为 2 的元素。
1echo ${#nums[*]}2# Output:3# 3向数组中添加元素也非常简单:
1colors=(white "${colors[@]}" green black)2echo ${colors[@]}3# Output:4# white red yellow dark blue green black上面的例子中,${colors[@]} 扩展为整个数组,并被置换到复合赋值语句中,接着,对数组 colors 的赋值覆盖了它原来的值。
用 unset 命令来从数组中删除一个元素:
1unset nums[0]2echo ${nums[@]}3# Output:4# 1 2💻 “示例源码”:
1#!/usr/bin/env bash23################### 创建数组 ###################4nums=( [ 2 ] = 2 [ 0 ] = 0 [ 1 ] = 1 )5colors=( red yellow "dark blue" )67################### 访问数组的单个元素 ###################8echo ${nums[1]}9# Output: 11011################### 访问数组的所有元素 ###################12echo ${colors[*]}13# Output: red yellow dark blue1415echo ${colors[@]}16# Output: red yellow dark blue1718printf "+ %s\n" ${colors[*]}19# Output:20# + red21# + yellow22# + dark23# + blue2425printf "+ %s\n" "${colors[*]}"26# Output:27# + red yellow dark blue2829printf "+ %s\n" "${colors[@]}"30# Output:31# + red32# + yellow33# + dark blue3435################### 访问数组的部分元素 ###################36echo ${nums[@]:0:2}37# Output:38# 0 13940################### 获取数组长度 ###################41echo ${#nums[*]}42# Output:43# 34445################### 向数组中添加元素 ###################46colors=( white "${colors[@]}" green black )47echo ${colors[@]}48# Output:49# white red yellow dark blue green black5051################### 从数组中删除元素 ###################52unset nums[ 0 ]53echo ${nums[@]}54# Output:55# 1 2下表列出了常用的算术运算符,假定变量 x 为 10,变量 y 为 20:
注意:条件表达式要放在方括号之间,并且要有空格,例如: [$x==$y] 是错误的,必须写成 [ $x == $y ]。
💻 “示例源码”:
1x=102y=2034echo "x=${x}, y=${y}"56val=`expr ${x} + ${y}`7echo "${x} + ${y} = $val"89val=`expr ${x} - ${y}`10echo "${x} - ${y} = $val"1112val=`expr ${x} \* ${y}`13echo "${x} * ${y} = $val"1415val=`expr ${y} / ${x}`16echo "${y} / ${x} = $val"1718val=`expr ${y} % ${x}`19echo "${y} % ${x} = $val"2021if [[ ${x} == ${y} ]]22then23 echo "${x} = ${y}"24fi25if [[ ${x} != ${y} ]]26then27 echo "${x} != ${y}"28fi2930# Output:31# x=10, y=2032# 10 + 20 = 3033# 10 - 20 = -1034# 10 * 20 = 20035# 20 / 10 = 236# 20 % 10 = 037# 10 != 20关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 x 为 10,变量 y 为 20:
💻 “示例源码”:
1x=102y=2034echo "x=${x}, y=${y}"56if [[ ${x} -eq ${y} ]]; then7 echo "${x} -eq ${y} : x 等于 y"8else9 echo "${x} -eq ${y}: x 不等于 y"10fi1112if [[ ${x} -ne ${y} ]]; then13 echo "${x} -ne ${y}: x 不等于 y"14else15 echo "${x} -ne ${y}: x 等于 y"16fi1718if [[ ${x} -gt ${y} ]]; then19 echo "${x} -gt ${y}: x 大于 y"20else21 echo "${x} -gt ${y}: x 不大于 y"22fi2324if [[ ${x} -lt ${y} ]]; then25 echo "${x} -lt ${y}: x 小于 y"26else27 echo "${x} -lt ${y}: x 不小于 y"28fi2930if [[ ${x} -ge ${y} ]]; then31 echo "${x} -ge ${y}: x 大于或等于 y"32else33 echo "${x} -ge ${y}: x 小于 y"34fi3536if [[ ${x} -le ${y} ]]; then37 echo "${x} -le ${y}: x 小于或等于 y"38else39 echo "${x} -le ${y}: x 大于 y"40fi4142# Output:43# x=10, y=2044# 10 -eq 20: x 不等于 y45# 10 -ne 20: x 不等于 y46# 10 -gt 20: x 不大于 y47# 10 -lt 20: x 小于 y48# 10 -ge 20: x 小于 y49# 10 -le 20: x 小于或等于 y下表列出了常用的布尔运算符,假定变量 x 为 10,变量 y 为 20:
💻 “示例源码”:
1x=102y=2034echo "x=${x}, y=${y}"56if [[ ${x} != ${y} ]]; then7 echo "${x} != ${y} : x 不等于 y"8else9 echo "${x} != ${y}: x 等于 y"10fi1112if [[ ${x} -lt 100 && ${y} -gt 15 ]]; then13 echo "${x} 小于 100 且 ${y} 大于 15 : 返回 true"14else15 echo "${x} 小于 100 且 ${y} 大于 15 : 返回 false"16fi1718if [[ ${x} -lt 100 || ${y} -gt 100 ]]; then19 echo "${x} 小于 100 或 ${y} 大于 100 : 返回 true"20else21 echo "${x} 小于 100 或 ${y} 大于 100 : 返回 false"22fi2324if [[ ${x} -lt 5 || ${y} -gt 100 ]]; then25 echo "${x} 小于 5 或 ${y} 大于 100 : 返回 true"26else27 echo "${x} 小于 5 或 ${y} 大于 100 : 返回 false"28fi2930# Output:31# x=10, y=2032# 10 != 20 : x 不等于 y33# 10 小于 100 且 20 大于 15 : 返回 true34# 10 小于 100 或 20 大于 100 : 返回 true35# 10 小于 5 或 20 大于 100 : 返回 false以下介绍 Shell 的逻辑运算符,假定变量 x 为 10,变量 y 为 20:
💻 “示例源码”:
1x=102y=2034echo "x=${x}, y=${y}"56if [[ ${x} -lt 100 && ${y} -gt 100 ]]7then8 echo "${x} -lt 100 && ${y} -gt 100 返回 true"9else10 echo "${x} -lt 100 && ${y} -gt 100 返回 false"11fi1213if [[ ${x} -lt 100 || ${y} -gt 100 ]]14then15 echo "${x} -lt 100 || ${y} -gt 100 返回 true"16else17 echo "${x} -lt 100 || ${y} -gt 100 返回 false"18fi1920# Output:21# x=10, y=2022# 10 -lt 100 && 20 -gt 100 返回 false23# 10 -lt 100 || 20 -gt 100 返回 true下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:
💻 “示例源码”:
1x="abc"2y="xyz"34echo "x=${x}, y=${y}"56if [[ ${x} = ${y} ]]; then7 echo "${x} = ${y} : x 等于 y"8else9 echo "${x} = ${y}: x 不等于 y"10fi1112if [[ ${x} != ${y} ]]; then13 echo "${x} != ${y} : x 不等于 y"14else15 echo "${x} != ${y}: x 等于 y"16fi1718if [[ -z ${x} ]]; then19 echo "-z ${x} : 字符串长度为 0"20else21 echo "-z ${x} : 字符串长度不为 0"22fi2324if [[ -n "${x}" ]]; then25 echo "-n ${x} : 字符串长度不为 0"26else27 echo "-n ${x} : 字符串长度为 0"28fi2930if [[ ${x} ]]; then31 echo "${x} : 字符串不为空"32else33 echo "${x} : 字符串为空"34fi3536# Output:37# x=abc, y=xyz38# abc = xyz: x 不等于 y39# abc != xyz : x 不等于 y40# -z abc : 字符串长度不为 041# -n abc : 字符串长度不为 042# abc : 字符串不为空文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:
💻 “示例源码”:
1file="/etc/hosts"23if [[ -r ${file} ]]; then4 echo "${file} 文件可读"5else6 echo "${file} 文件不可读"7fi8if [[ -w ${file} ]]; then9 echo "${file} 文件可写"10else11 echo "${file} 文件不可写"12fi13if [[ -x ${file} ]]; then14 echo "${file} 文件可执行"15else16 echo "${file} 文件不可执行"17fi18if [[ -f ${file} ]]; then19 echo "${file} 文件为普通文件"20else21 echo "${file} 文件为特殊文件"22fi23if [[ -d ${file} ]]; then24 echo "${file} 文件是个目录"25else26 echo "${file} 文件不是个目录"27fi28if [[ -s ${file} ]]; then29 echo "${file} 文件不为空"30else31 echo "${file} 文件为空"32fi33if [[ -e ${file} ]]; then34 echo "${file} 文件存在"35else36 echo "${file} 文件不存在"37fi3839# Output:(根据文件的实际情况,输出结果可能不同)40# /etc/hosts 文件可读41# /etc/hosts 文件可写42# /etc/hosts 文件不可执行43# /etc/hosts 文件为普通文件44# /etc/hosts 文件不是个目录45# /etc/hosts 文件不为空46# /etc/hosts 文件存在跟其它程序设计语言一样,Bash 中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在[[ ]]里的表达式。
由[[ ]](sh 中是[ ])包起来的表达式被称作 检测命令 或 基元。这些表达式帮助我们检测一个条件的结果。这里可以找到有关 bash 中单双中括号区别的答案。
共有两个不同的条件表达式:if 和 case。
(1)if 语句
if 在使用上跟其它语言相同。如果中括号里的表达式为真,那么 then 和 fi 之间的代码会被执行。fi 标志着条件代码块的结束。
1# 写成一行2if [[ 1 -eq 1 ]]; then echo "1 -eq 1 result is: true"; fi3# Output: 1 -eq 1 result is: true45# 写成多行6if [[ "abc" -eq "abc" ]]7then8 echo ""abc" -eq "abc" result is: true"9fi10# Output: abc -eq abc result is: true(2)if else 语句
同样,我们可以使用 if..else 语句,例如:
1if [[ 2 -ne 1 ]]; then2 echo "true"3else4 echo "false"5fi6# Output: true(3)if elif else 语句
有些时候,if..else 不能满足我们的要求。别忘了 if..elif..else,使用起来也很方便。
💻 “示例源码”:
1x=102y=203if [[ ${x} > ${y} ]]; then4 echo "${x} > ${y}"5elif [[ ${x} < ${y} ]]; then6 echo "${x} < ${y}"7else8 echo "${x} = ${y}"9fi10# Output: 10 < 20如果你需要面对很多情况,分别要采取不同的措施,那么使用 case 会比嵌套的 if 更有用。使用 case 来解决复杂的条件判断,看起来像下面这样:
💻 “示例源码”:
1exec2case ${oper} in3 "+")4 val=`expr ${x} + ${y}`5 echo "${x} + ${y} = ${val}"6 ;;7 "-")8 val=`expr ${x} - ${y}`9 echo "${x} - ${y} = ${val}"10 ;;11 "*")12 val=`expr ${x} \* ${y}`13 echo "${x} * ${y} = ${val}"14 ;;15 "/")16 val=`expr ${x} / ${y}`17 echo "${x} / ${y} = ${val}"18 ;;19 *)20 echo "Unknown oper!"21 ;;22esac每种情况都是匹配了某个模式的表达式。|用来分割多个模式,)用来结束一个模式序列。第一个匹配上的模式对应的命令将会被执行。*代表任何不匹配以上给定模式的模式。命令块儿之间要用;;分隔。
循环其实不足为奇。跟其它程序设计语言一样,bash 中的循环也是只要控制条件为真就一直迭代执行的代码块。
Bash 中有四种循环:
for 与它在 C 语言中的姊妹非常像。看起来是这样:
1for arg in elem1 elem2 ... elemN2do3 ### 语句4done在每次循环的过程中,arg 依次被赋值为从 elem1 到 elemN。这些值还可以是通配符或者大括号扩展。
当然,我们还可以把 for 循环写在一行,但这要求 do 之前要有一个分号,就像下面这样:
1for i in {1..5}; do echo $i; done还有,如果你觉得 for..in..do 对你来说有点奇怪,那么你也可以像 C 语言那样使用 for,比如:
1for (( i = 0; i < 10; i++ )); do2 echo $i3done当我们想对一个目录下的所有文件做同样的操作时,for 就很方便了。举个例子,如果我们想把所有的.bash 文件移动到 script 文件夹中,并给它们可执行权限,我们的脚本可以这样写:
💻 “示例源码”:
1DIR=/home/zp2for FILE in ${DIR}/*.sh; do3 mv "$FILE" "${DIR}/scripts"4done5# 将 /home/zp 目录下所有 sh 文件拷贝到 /home/zp/scriptswhile 循环检测一个条件,只要这个条件为 真,就执行一段命令。被检测的条件跟 if..then 中使用的基元并无二异。因此一个 while 循环看起来会是这样:
1while [[ condition ]]2do3 ### 语句4done跟 for 循环一样,如果我们把 do 和被检测的条件写到一行,那么必须要在 do 之前加一个分号。
💻 “示例源码”:
1### 0到9之间每个数的平方2x=03while [[ ${x} -lt 10 ]]; do4 echo $((x * x))5 x=$((x + 1))6done7# Output:8# 09# 110# 411# 912# 1613# 2514# 3615# 4916# 6417# 81until 循环跟 while 循环正好相反。它跟 while 一样也需要检测一个测试条件,但不同的是,只要该条件为 假 就一直执行循环:
💻 “示例源码”:
1x=02until [[ ${x} -ge 5 ]]; do3 echo ${x}4 x=`expr ${x} + 1`5done6# Output:7# 08# 19# 210# 311# 4select 循环帮助我们组织一个用户菜单。它的语法几乎跟 for 循环一致:
1select answer in elem1 elem2 ... elemN2do3 ### 语句4doneselect 会打印 elem1..elemN 以及它们的序列号到屏幕上,之后会提示用户输入。通常看到的是 $?(PS3 变量)。用户的选择结果会被保存到 answer 中。如果 answer 是一个在 1..N 之间的数字,那么语句会被执行,紧接着会进行下一次迭代 —— 如果不想这样的话我们可以使用 break 语句。
💻 “示例源码”:
1#!/usr/bin/env bash23PS3="Choose the package manager: "4select ITEM in bower npm gem pip5do6echo -n "Enter the package name: " && read PACKAGE7case ${ITEM} in8 bower) bower install ${PACKAGE} ;;9 npm) npm install ${PACKAGE} ;;10 gem) gem install ${PACKAGE} ;;11 pip) pip install ${PACKAGE} ;;12esac13break # 避免无限循环14done这个例子,先询问用户他想使用什么包管理器。接着,又询问了想安装什么包,最后执行安装操作。
运行这个脚本,会得到如下输出:
1$ ./my_script21) bower32) npm43) gem54) pip6Choose the package manager: 27Enter the package name: gitbook-cli如果想提前结束一个循环或跳过某次循环执行,可以使用 shell 的 break 和 continue 语句来实现。它们可以在任何循环中使用。
break 语句用来提前结束当前循环。
continue 语句用来跳过某次迭代。
💻 “示例源码”:
1# 查找 10 以内第一个能整除 2 和 3 的正整数2i=13while [[ ${i} -lt 10 ]]; do4 if [[ $((i % 3)) -eq 0 ]] && [[ $((i % 2)) -eq 0 ]]; then5 echo ${i}6 break;7 fi8 i=`expr ${i} + 1`9done10# Output: 6💻 “示例源码”:
1# 打印10以内的奇数2for (( i = 0; i < 10; i ++ )); do3 if [[ $((i % 2)) -eq 0 ]]; then4 continue;5 fi6 echo ${i}7done8# Output:9# 110# 311# 512# 713# 9bash 函数定义语法如下:
1[ function ] funname [()] {2 action;3 [return int;]4}提示
💡 说明:
函数定义时,function 关键字可有可无。
函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值。
函数返回值在调用该函数后通过 $? 来获得。
所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。
💻 “示例源码”:
1#!/usr/bin/env bash23calc(){4 PS3="choose the oper: "5 select oper in + - \* / # 生成操作符选择菜单6 do7 echo -n "enter first num: " && read x # 读取输入参数8 echo -n "enter second num: " && read y # 读取输入参数9 exec10 case ${oper} in11 "+")12 return $((${x} + ${y}))13 ;;14 "-")15 return $((${x} - ${y}))16 ;;17 "*")18 return $((${x} * ${y}))19 ;;20 "/")21 return $((${x} / ${y}))22 ;;23 *)24 echo "${oper} is not support!"25 return 026 ;;27 esac28 break29 done30}31calc32echo "the result is: $?" # $? 获取 calc 函数返回值执行结果:
1$ ./function-demo.sh21) +32) -43) *54) /6choose the oper: 37enter first num: 108enter second num: 109the result is: 100位置参数是在调用一个函数并传给它参数时创建的变量。
位置参数变量表:
💻 “示例源码”:
1#!/usr/bin/env bash23x=04if [[ -n $1 ]]; then5 echo "第一个参数为:$1"6 x=$17else8 echo "第一个参数为空"9fi1011y=012if [[ -n $2 ]]; then13 echo "第二个参数为:$2"14 y=$215else16 echo "第二个参数为空"17fi1819paramsFunction(){20 echo "函数第一个入参:$1"21 echo "函数第二个入参:$2"22}23paramsFunction ${x} ${y}执行结果:
1$ ./function-demo2.sh2第一个参数为空3第二个参数为空4函数第一个入参:05函数第二个入参:067$ ./function-demo2.sh 10 208第一个参数为:109第二个参数为:2010函数第一个入参:1011函数第二个入参:20执行 ./variable-demo4.sh hello world ,然后在脚本中通过 $1、$2 … 读取第 1 个参数、第 2 个参数。。。
另外,还有几个特殊字符用来处理参数:
💻 “示例源码”:
1runner() {2 return 03}45name=zp6paramsFunction(){7 echo "函数第一个入参:$1"8 echo "函数第二个入参:$2"9 echo "传递到脚本的参数个数:$#"10 echo "所有参数:"11 printf "+ %s\n" "$*"12 echo "脚本运行的当前进程 ID 号:$$"13 echo "后台运行的最后一个进程的 ID 号:$!"14 echo "所有参数:"15 printf "+ %s\n" "$@"16 echo "Shell 使用的当前选项:$-"17 runner18 echo "runner 函数的返回值:$?"19}20paramsFunction 1 "abc" "hello, \"zp\""21# Output:22# 函数第一个入参:123# 函数第二个入参:abc24# 传递到脚本的参数个数:325# 所有参数:26# + 1 abc hello, "zp"27# 脚本运行的当前进程 ID 号:2640028# 后台运行的最后一个进程的 ID 号:29# 所有参数:30# + 131# + abc32# + hello, "zp"33# Shell 使用的当前选项:hB34# runner 函数的返回值:0扩展 发生在一行命令被分成一个个的 记号(tokens) 之后。换言之,扩展是一种执行数学运算的机制,还可以用来保存命令的执行结果,等等。
感兴趣的话可以阅读关于 shell 扩展的更多细节。
大括号扩展让生成任意的字符串成为可能。它跟 文件名扩展 很类似,举个例子:
1echo beg{i,a,u}n ### begin began begun大括号扩展还可以用来创建一个可被循环迭代的区间。
1echo {0..5} ### 0 1 2 3 4 52echo {00..8..2} ### 00 02 04 06 08命令置换允许我们对一个命令求值,并将其值置换到另一个命令或者变量赋值表达式中。当一个命令被``或 $()包围时,命令置换将会执行。举个例子:
1now=`date +%T`2### or3now=$(date +%T)45echo $now ### 19:08:26在 bash 中,执行算数运算是非常方便的。算数表达式必须包在 $(( ))中。算数扩展的格式为:
1result=$(( ((10 + 5*3) - 7) / 2 ))2echo $result ### 9在算数表达式中,使用变量无需带上 $ 前缀:
1x=42y=73echo $(( x + y )) ### 114echo $(( ++x + y++ )) ### 125echo $(( x + y )) ### 13单引号和双引号之间有很重要的区别。在双引号中,变量引用或者命令置换是会被展开的。在单引号中是不会的。举个例子:
1echo "Your home: $HOME" ### Your home: /Users/<username>2echo 'Your home: $HOME' ### Your home: $HOME当局部变量和环境变量包含空格时,它们在引号中的扩展要格外注意。随便举个例子,假如我们用 echo 来输出用户的输入:
1INPUT="A string with strange whitespace."2echo $INPUT ### A string with strange whitespace.3echo "$INPUT" ### A string with strange whitespace.调用第一个 echo 时给了它 5 个单独的参数 —— $INPUT 被分成了单独的词,echo 在每个词之间打印了一个空格。第二种情况,调用 echo 时只给了它一个参数(整个 $INPUT 的值,包括其中的空格)。
来看一个更严肃的例子:
1FILE="Favorite Things.txt"2cat $FILE ### 尝试输出两个文件: `Favorite` 和 `Things.txt`3cat "$FILE" ### 输出一个文件: `Favorite Things.txt`尽管这个问题可以通过把 FILE 重命名成 Favorite-Things.txt 来解决,但是,假如这个值来自某个环境变量,来自一个位置参数,或者来自其它命令(find, cat, 等等)呢。因此,如果输入 可能 包含空格,务必要用引号把表达式包起来。
Bash 有很强大的工具来处理程序之间的协同工作。使用流,我们能将一个程序的输出发送到另一个程序或文件,因此,我们能方便地记录日志或做一些其它我们想做的事。
管道给了我们创建传送带的机会,控制程序的执行成为可能。
学习如何使用这些强大的、高级的工具是非常非常重要的。
Bash 接收输入,并以字符序列或 字符流 的形式产生输出。这些流能被重定向到文件或另一个流中。
有三个文件描述符:
重定向让我们可以控制一个命令的输入来自哪里,输出结果到什么地方。这些运算符在控制流的重定向时会被用到:
以下是一些使用重定向的例子:
1### ls的结果将会被写到list.txt中2ls -l > list.txt34### 将输出附加到list.txt中5ls -a >> list.txt67### 所有的错误信息会被写到errors.txt中8grep da * 2> errors.txt910### 从errors.txt中读取输入11less < errors.txt如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
1$ command > /dev/null/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出”的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
1$ command > /dev/null 2>&1shell 提供了用于 debug 脚本的工具。
如果想采用 debug 模式运行某脚本,可以在其 shebang 中使用一个特殊的选项:
1#!/bin/bash optionsoptions 是一些可以改变 shell 行为的选项。下表是一些可能对你有用的选项:
举个例子,如果我们在脚本中指定了 -x 例如:
1#!/bin/bash -x23for (( i = 0; i < 3; i++ )); do4 echo $i5done这会向 stdout 打印出变量的值和一些其它有用的信息:
1$ ./my_script2+ (( i = 0 ))3+ (( i < 3 ))4+ echo 0506+ (( i++ ))7+ (( i < 3 ))8+ echo 19110+ (( i++ ))11+ (( i < 3 ))12+ echo 213214+ (( i++ ))15+ (( i < 3 ))有时我们值需要 debug 脚本的一部分。这种情况下,使用 set 命令会很方便。这个命令可以启用或禁用选项。使用-启用选项,+禁用选项:
💻 “示例源码”:
1# 开启 debug2set -x3for (( i = 0; i < 3; i++ )); do4 printf ${i}5done6# 关闭 debug7set +x8# Output:9# + (( i = 0 ))10# + (( i < 3 ))11# + printf 012# 0+ (( i++ ))13# + (( i < 3 ))14# + printf 115# 1+ (( i++ ))16# + (( i < 3 ))17# + printf 218# 2+ (( i++ ))19# + (( i < 3 ))20# + set +x2122for i in {1..5}; do printf ${i}; done23printf "\n"24# Output: 12345Oracle 一键巡检自动生成 Word 报告
Oracle 一键安装合集
Oracle一键安装脚本的 21 个疑问与解答
Oracle一键巡检脚本的 21 个疑问与解答
全网首发:Oracle 23ai 一键安装脚本(非 RPM)
Oracle 19C 最新 RU 补丁 19.24 ,一键安装!
Oracle Linux 7.9 一键安装 Oracle 19C
RedHat 9.4(aarch64) 一键安装 Oracle 19C
openEuler 22.03 LTS SP4 一键安装 Oracle 19C RAC
RHEL 7.9 一键安装 Oracle 19C 19.23 RAC
Oracle DataGuard GAP 修复手册
优化 Oracle:最佳实践与开发规范
DBA 必备:Linux 软件源配置全攻略
Linux 一键配置时钟同步全攻略
感谢您的阅读,这里是 Lucifer三思而后行,欢迎点赞+关注,我会持续分享数据库知识、运维技巧。