Tutorial-bash

    科技2026-03-04  3

    ############# # 参考资料 # ############# # 《阮一峰 Bash 脚本教程》 # https://www.bookstack.cn/read/bash-tutorial/README.md ############# # 0.常用操作 # ############# #0. 常用命令 readonly let expr history pushd popd dirs shif getopts mktemp #为安全创建临时文件而设计,它支持唯一文件名和清除机制,因此可以减轻安全攻击的风险 trap #用来在 Bash 脚本中响应系统信号 uname -rm #返回当前机器的信息,-r:内核的版本 -m:CPU 架构 service --status-all #查看当前正在运行的服务,+表示正在运行,-表示已经停止,?表示service命令不了解相关信息 #1. 快捷键 Ctrl + L #清除屏幕并将当前行移到页面顶部。 Ctrl + C #中止当前正在执行的命令。 Shift + PageUp #向上滚动。 Shift + PageDown #向下滚动。 Ctrl + U #从光标位置删除到行首。 Ctrl + K #从光标位置删除到行尾。 Ctrl + D #关闭 Shell 会话。 ↑,↓ #浏览已执行命令的历史记录。 #2. 脚本除错 #2.1 编写 Shell 脚本的时候,一定要考虑到命令失败的情况,否则很容易出错。 #先判断目录$dir_name是否存在,然后才执行其他操作 [[ -d $dir_name ]] && cd $dir_name && rm * [[ -d $dir_name ]] && cd $dir_name && echo rm * #echo rm *不会删除文件,只会打印出来要删除的文件 #2.2 bash的-x参数可以在执行每一行命令之前,打印该命令 bash -x script.sh #! /bin/bash -x # trouble: script to demonstrate common errors number=1 if [ $number = 1 ]; then echo "Number is equal to 1." else echo "Number is not equal to 1." fi export PS4='$LINENO + ' #2.3 环境变量 $LINENO #变量LINENO返回它在脚本里面的行号 $FUNCNAME #变量FUNCNAME返回一个数组,内容是当前的函数调用堆栈 $BASH_SOURCE #变量BASH_SOURCE返回一个数组,内容是当前的脚本调用堆栈 $BASH_LINENO #变量BASH_LINENO返回一个数组,内容是每一轮调用对应的行号 #3. set命令 #set命令用来修改子 Shell 环境的运行参数,即定制环境 set #显示所有的环境变量和 Shell 函数 set -u == set -o nounset #一般执行脚本时,如果遇到不存在的变量,Bash 默认忽略它。脚本在头部加上set -u,遇到不存在的变量bash就会报错,并停止执行 set -x == set -o xtrace #用来在运行结果之前,先输出执行的那一行命令。命令会先打印出来,行首以+表示。这对于调试复杂的脚本是很有用的。 set +x #关闭命令输出 #只对特定的代码段打开命令输出 #!/bin/bash number=1 set -x if [ $number = "1" ]; then echo "Number equals 1" else echo "Number does not equal 1" fi set +x #Bash 的错误处理 #如果脚本里面有运行失败的命令(返回值非0),Bash 默认会继续执行后面的命令。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。 command || exit 1 #只要command有非零返回值,脚本就会停止执行 #如果停止执行之前需要完成多个操作,就要采用下面三种写法 # 写法一 command || { echo "command failed"; exit 1; } # 写法二 if ! command; then echo "command failed"; exit 1; fi # 写法三 command if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi set -e == set -o errexit #据返回值来判断,一个命令是否运行失败。脚本只要发生错误,就终止执行。 #可以暂时关闭set -e,当命令执行结束后,再重新打开set -e。 set +e command1 command2 set -e #命令即使执行失败,脚本也不会终止执行 command || true #只要一个子命令失败,整个管道命令就失败,脚本就会终止执行 set -o pipefail set -n #等同于set -o noexec,不运行命令,只检查语法是否正确。 set -f #等同于set -o noglob,表示不对通配符进行文件名扩展。 set -v #等同于set -o verbose,表示打印 Shell 接收到的每一行输入。 #终极命令,建议放在所有 Bash 脚本的头部 set -euxo pipefail bash -euxo pipefail script.sh shopt -suq #用来调整 Shell 的参数 -s:用来打开某个参数 -u:用来关闭某个参数 -q:作用也是查询某个参数是否打开,但不是直接输出查询结果,而是通过命令的执行状态($?)表示查询结果。如果状态为0,表示该参数打开;如果为1,表示该参数关闭。 #主要用于脚本,供if条件结构使用 if shopt -q globstar; then ... if #4. bash命令 #4.1 登录 Session #登录 Session 是用户登录系统以后,系统为用户开启的原始 Session,通常需要用户输入用户名和密码进行登录。 #登录 Session 一般进行整个系统环境的初始化,启动的初始化脚本依次如下。 /etc/profile #所有用户的全局配置脚本。 /etc/profile.d #目录里面所有.sh文件 ~/.bash_profile #用户的个人配置脚本。如果该脚本存在,则执行完就不再往下执行。 ~/.bash_login #如果~/.bash_profile没找到,则尝试执行这个脚本(C shell 的初始化脚本)。如果该脚本存在,则执行完就不再往下执行。 ~/.profile #如果~/.bash_profile和~/.bash_login都没找到,则尝试读取这个脚本(Bourne shell 和 Korn shell 的初始化脚本)。 bash --login #会强制执行登录 Session 会执行的脚本 bash --noprofile #会跳过上面这些 Profile 脚本 #4.2 非登录 Session #非登录 Session 是用户进入系统以后,手动新建的 Session,这时不会进行环境初始化。 #非登录 Session 的初始化脚本依次如下。 #由于每次执行 Bash 脚本,都会新建一个非登录 Session,所以~/.bashrc也是每次执行脚本都会执行的 /etc/bash.bashrc #对全体用户有效。 ~/.bashrc #仅对当前用户有效。 bash --norc #可以禁止在非登录 Session 执行~/.bashrc脚本 bash --rcfile testr #指定另一个脚本代替.bashrc ~/.bash_logout #脚本在每次退出 Session 时执行,通常用来做一些清理工作和记录工作,比如删除临时文件,记录用户在本次 Session 花费的时间。 #4.3 启动选项 bash -n scriptname #不运行脚本,只检查是否有语法错误 bash -v scriptname #输出每一行语句运行结果前,会先输出该行语句 bash -x scriptname #每一个命令处理完以后,先输出该命令,再进行下一个命令的处 #4.4 键盘绑定 /etc/inputrc #全局的键盘绑定文件 .inputrc #在主目录创建自己的键盘绑定文件,要在其中加入include /etc/inputrc,保证全局绑定不会被遗漏 #5. 命令提示符 #5.1 环境变量 PS1 echo $PS1 export PS1="\A \h \$ " \a #响铃,计算机发出一记声音。 \d #以星期、月、日格式表示当前日期,例如“Mon May 26”。 \h #本机的主机名。 \H #完整的主机名。 \j #运行在当前 Shell 会话的工作数。 \l #当前终端设备名。 \n #一个换行符。 \r #一个回车符。 \s #Shell 的名称。 \t #24小时制的hours:minutes:seconds格式表示当前时间。 \T #12小时制的当前时间。 \@ #12小时制的AM/PM格式表示当前时间。 \A #24小时制的hours:minutes表示当前时间。 \u #当前用户名。 \v #Shell 的版本号。 \V #Shell 的版本号和发布号。 \w #当前的工作路径。 \W #当前目录名。 \! #当前命令在命令历史中的编号。 \# #当前 shell 会话中的命令数。 \$ #普通用户显示为$字符,根用户显示为#字符。 \[ #非打印字符序列的开始标志。 \] #非打印字符序列的结束标志。 #5.2 颜色 #设置前景颜色 \033[0;30m #黑色 \033[1;30m #深灰色 \033[0;31m #红色 \033[1;31m #浅红色 \033[0;32m #绿色 \033[1;32m #浅绿色 \033[0;33m #棕色 \033[1;33m #黄色 \033[0;34m #蓝色 \033[1;34m #浅蓝色 \033[0;35m #粉红 \033[1;35m #浅粉色 \033[0;36m #青色 \033[1;36m #浅青色 \033[0;37m #浅灰色 \033[1;37m #白色 export PS1='\[\033[0;31m\]<\u@\h \W>\$' #将提示符设为红色 export PS1='\[\033[0;31m\]<\u@\h \W>\$\[\033[00m\]' #可以在结尾添加另一个特殊代码\[\033[00m\],表示将其后的文本恢复到默认颜色 export PS1='\[\033[0;41m\]<\u@\h \W>\$\[\033[0m\] ' #带有红色背景的提示符 #设置背景颜色 \033[0;40m #蓝色 \033[1;44m #黑色 \033[0;41m #红色 \033[1;45m #粉红 \033[0;42m #绿色 \033[1;46m #青色 \033[0;43m #棕色 \033[1;47m #浅灰色 #5.3 环境变量 PS2,PS3,PS4 PS2 #环境变量PS2是命令行折行输入时系统的提示符,默认为> PS3 #环境变量PS3是使用select命令时,系统输入菜单的提示符 PS4 #环境变量PS4默认为+ #6. 命名管道 process1 > named_pipe + process2 < named_pipe = process1 | process2 mkfifo pipe1 #使用 mkfifo 命令能够创建命令管道 ls -l pipe1 ls -l > pipe1 #按下 Enter 按键之后,命令将会挂起,这是因为在管道的另一端没有任何接受数据 cat < pipe1 #一旦我们绑定一个进程到管道的另一端,该进程开始从管道中读取输入的时候,这种情况会消失 #7. 重定向 #重定向指的是将命令行输出写入指定位置。 cmd1 | cmd2 #Pipe; take standard output of cmd1 as standard input to cmd2. > file #Direct standard output to file. < file #Take standard input from file. >> file #Direct standard output to file; append to file if it already exists. >| file #Force standard output to file even if noclobber is set. n>| file #Force output to file from file descriptor n even if noclobber is set. <> file #Use file as both standard input and standard output. n<> file #Use file as both input and output for file descriptor n. #<< label #Here-document; see text. n > file #Direct file descriptor n to file. n < file #Take file descriptor n from file. n >> file #Direct file descriptor n to file; append to file if it already exists. n>& #Duplicate standard output to file descriptor n. n<& #Duplicate standard input from file descriptor n. n>&m #File descriptor n is made to be a copy of the output file descriptor. n<&m #File descriptor n is made to be a copy of the input file descriptor. &>file #Directs standard output and standard error to file. <&- #Close the standard input. >&- #Close the standard output. n>&- #Close the output from file descriptor n. n<&- #Close the input from file descriptor n. n>&word #If n is not specified, the standard output (file descriptor 1) is used. If the digits in word do not specify a file descriptor open for output, a redirection error occurs. As a special case, if n is omitted, and word does not expand to one or more digits, the standard output and standard error are redirected as described previously. n<&word #If word expands to one or more digits, the file descriptor denoted by n is made to be a copy of that file descriptor. If the digits in word do not specify a file descriptor open for input, a redirection error occurs. If word evaluates to -, file descriptor n is closed. If n is not specified, the standard input (file descriptor 0) is used. n>&digit- #Moves the file descriptor digit to file descriptor n, or the standard output (file descriptor 1) if n is not specified. n<&digit- #Moves the file descriptor digit to file descriptor n, or the standard input (file descriptor 0) if n is not specified. digit is closed after being duplicated to n. ls -l /usr/bin > ls-output.txt #>用来将标准输出重定向到指定文件。如果重定向后的指定文件已经存在,就会被覆盖,不会有任何提示。 > ls-output.txt #如果命令没有任何输出,那么重定向之后,得到的是一个长度为0的文件。因此,>具有创建新文件或改写现存文件、将其改为长度0的作用。 ls -l /usr/bin >> ls-output.txt #>>用来将标准输出重定向追加到指定文件 ls -l /bin/usr 2> ls-error.txt #2>用来将标准错误重定向到指定文件 ls -l /bin/usr > ls-output.txt 2>&1 or ls -l /bin/usr &> ls-output.txt #标准输出和标准错误,可以重定向到同一个文件 ls -l /bin/usr &>> ls-output.txt #追加到同一个文件 ls -l /bin/usr 2> /dev/null #如果不希望输出错误信息,可以将它重定向到一个特殊文件/dev/null ls -l /usr/bin | less #|用于将一个命令的标准输出,重定向到另一个命令的标准输入 #标准错误重定向 invalid_input () { echo "Invalid input '$REPLY'" >&2 exit 1 } read -p "Enter a single item > " [[ -z $REPLY ]] && invalid_input ls /usr/bin | tee ls.txt | grep zip #tee命令用于同时将标准输出重定向到文件,以及另一个命令的标准输入 echo $(ls) or ls -l $(which cp) #命令替换(command substitution)指的是将一个命令的输出,替换进入另一个命令。$(command)表示命令替换,另一种写法是使用反引号。 #basename命令清除 一个路径名的开头部分,只留下一个文件的基本名称 #!/bin/bash # file_info: simple file information program PROGNAME=$(basename $0) if [[ -e $1 ]]; then echo -e "\nFile Type:" file $1 echo -e "\nFile Status:" stat $1 else echo "$PROGNAME: usage: $PROGNAME file" >&2 exit 1 fi #8. 命令的连续执行 # 第一个命令执行完,执行第二个命令 command1; command2 # 只有第一个命令成功执行完(退出码0),才会执行第二个命令 command1 && command2 # 只有第一个命令执行失败(退出码非0),才会执行第二个命令 command1 || command2 #组命令 { command1; command2; [command3; ...] } #子 shell (command1; command2; [command3;...]) { ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt (ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt { ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr #9. 反引号、$()和${} #9.1 反引号与$()用于命令替换 #反引号和$()的作用相同,用于命令替换(command substitution),即完成引用的命令的执行,将其结果替换出来 echo `date '--date=1 hour ago' +%Y-%m-%d-%H` #或者 echo $(date '--date=1 hour ago' +%Y-%m-%d-%H) #在多层次的复合替换中,里层的反引号需要转义处理(\`) ,而$()则比较直观 command1 `command2 \`command3\`` #或者换成$() command1 $(command2 $(command3)) #反引号中对于反斜杠有特殊的处理,使用反协议对Shell特殊字符进行转义时需要两个反斜杠,而$()中只需要使用一个反斜杠 var1=`echo \$HOME` #使用一个反斜杠无法完成对$符的转义 var2=`echo \\$HOME` var3=$(echo \$HOME) #反引号是一个老的用法,$()是新的用法,无论是在学习还是实际工作中,建议使用$() #9.2 ${}用于变量替换 #1. 直接变量替换 echo ${A}B #一般情况下,$var与${var}并没有区别,但是用${ }会比较精确的界定变量名称的范围 #2. 特殊变量替换 #${} 除了直接替换变量内容,还有一些用于字符串变量的特殊功能 file="/dir1/dir2/dir3/my.file.txt" #2.1 ${:}与${::}用于字符串提取 ${var:n} #若n为正数,n从0开始,表示在变量var中提取第n个字符到末尾的所有字符。若n为负数,提取字符串最后面n的绝对值个字符,使用时在冒号后面加空格或一个算术表达式或整个num加上括号,如${var: -2}、${var:1−3}或 ${var:(-2)}均表示提取最后两个字符。 ${file:1} #提取第1个字符及其后面的所有字符:dir1//dir2/dir3/my.file.txt ${file: -3} #提取最后3个字符,注意冒号后面添加一个空格:txt ${file:1-4} #提取最后3个字符,冒号后面不需要添加空格:txt ${file:(-3)} #提取最后3个字符,冒号后面不需要添加空格:txt ${var:n1:n2} #${var:n1:n2}用于提取从下标n1开始后面n2个字符,其中下标n1与n2从0开始 ${file:0:5}:提取最左边的5个字符:/dir1 ${file:5:5}:提取从第5个字符开始右边的连续5个字符:/dir2 #2.2 ${/}与${//}用于字符串模式匹配替换 #${var/pattern/pattern}表示将var字符串的第一个匹配的pattern替换为另一个pattern。不改变原变量。 ${file/dir/path} #将第一个dir替换为path:/path1/dir2/dir3/my.file.txt ${file//dir/path} #将全部dir替换为path:/path1/path2/path3/my.file.txt #2.3 ${#}、${##}、${%}与${%%}用于字符串模式匹配截断 #可以过滤掉符合指定规则的字符串,不改变原变量 ##是去掉左边(在鉴盘上#在$之左边) #%是去掉右边(在鉴盘上%在$之右边) #一个符号是最小匹配,两个符号是最大匹配。 ${file#*/} #拿掉第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt ${file##*/} #拿掉最后一个 / 及其左边的字符串:my.file.txt ${file#*.} #拿掉第一个 . 及其左边的字符串:file.txt ${file##*.} #拿掉最后一个 . 及其左边的字符串:txt ${file%/*} #拿掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3 ${file%%/*} #拿掉第一个 / 及其右边的字符串:(空值) ${file%.*} #拿掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file ${file%%.*} #拿掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my #10. 特殊字符 #1、;分号 ifdown eth0;ifup eth0 #连续运行命令 #2、| 管道 #正则表达式中表示或者 echo "ooooee" |egrep '(oo|ee)'{2} #表示匹配 oooo 或者 eeee 的字符 #前面命令的标准输出作为后面命令的标准输入 ifconfig|grep eth0 #表示ifconfig查出来的信息然后过滤出eth0的这一行 #3、& #将命令放到后台执行 mysqld_safe --user=mysql & #将MySQL放到后台启动 #表示标准输出和标准错误输出 ifconfig &>/dev/null #将ifconfig执行得到的结果输出到/dev/null里面 #4、&& #前面命令返回值为0才执行后面的命令 ls && echo "ok" #5、|| #前面命令返回值为非0才执行后面的命令 lls || echo "ok" #6、# 井号 # # 表示注释 # $# 表示位置参数的个数 echo $# # ${#变量名} 表示变量的长度 echo ${#a} # ${#变量名[@]} 表示数组的个数 echo ${#a[@]} #7、!惊叹号 #将命令或者条件表达式的返回值取反 if ! [ 1<2 ]; then echo 'ok'; else echo 'no'; fi #执行历史命令 #vi或者ftp中执行外部shell命令 #间接应用变量 #8、$ 美元符号 #取变量的值 #正则表达式表示行尾 #9、> 大于号 #输出重定向 echo '123' >test.txt #表示将123 输入到文件test.txt中 条件测试中的大于号 #11、< 小于号 #输入重定向 #条件测试中的小于号 #= 等号 #变量赋值 a=10 #设置变量a=10 #条件测试中的等号 [ a=b ] #判断变量a是否等于b #数值比较 == (( a==20 )) #判断变量a是否等于20 #12、+ 加号 #算术运算中的加号 1+3 #正则表达式中1个或多个前面的字符 ab+c #表示匹配ab和c之间有1个或者多个 字符 #13、>> #输出重定向追加 echo "123" >> test.txt #将123追加到文件test.txt中 #14、<< #here document # passwd <<end > 123 > 123 > end #15、- 减号 #算术运算中的减号 10-2 #命令的选项 ls -l #上一次工作目录 cd - #通配符和正则表达式中表示范围 [a-z] #表示输出流或输入流 #将前面的输出 ,通过管道交给后面的命令,前面的压缩,后面的解压 #16、'' 单引号 #解决变量赋值空格的问题 #阻止shell替换 #17、"" 双引号 #解决变量赋值空格的问题 #阻止shell部分字符替换,对$、!等无效 #18、`` 反引号 相当于 $() #命令行替换 #19、% 百分号 echo $((100%10)) #就是100除以10的余数为0 #vi中替换操作中表示所有行(末行模式下,替换所有前面加 %) 在末行模式下输入 :% s/D/d #表示将文本中的所有的D替换为d #20、() 单圆括号 #子shell中执行命令,会继承父shell的变量 #括起数组元素 #21、(()) 双圆括号 #算术运算 echo $((10/2)) #结果就是5 #整数比较测试 (( 10>2 )) #判断10是否大于2 #22、[] 单方括号 #通配符和正则中表示匹配括号中的任意一个字符 [abc] #表示匹配abc中的任意一个字符 #条件测试表达式 [ -f /etc/passwd ] #测试是不是文件 #数组中下标括号 echo ${a[0]} #表示取数组中下标为0的值 #23、[[]] 双方括号 #字符串比较测试 [[a=b]] #用来字符串的比较 #24、. 英文句点号 #正则中表示任意1个字符 a...b #表示 匹配 a和b之间夹三个字符的字符串 #当前shell执行脚本命令 ./test.sh #执行当前路径下的shell脚本test.sh #表示当前目录 cd ./bgk #进入当前目录下的bgk目录下 #25、{} 大括号 #通配符扩展 abc{1,2,3} #正则表达式中表示范围 a{3} #匹配3个 a #for i in {1...10} 循环指定范围 #匿名函数 { cmd1;cmd2;cmd3;} &> /dev/null #{ } 里面的命令,是在当前shell执行 注意: { } 第一条命令前面要有空格,后面的命令要有分号 #括起变量名 ${abc}a #26、/ 正斜杠 #算术运算中的除法 echo $((10/2)) #结果就是5 #根目录或路径分割符 cd /usr/local/ #表示路径 #27、^ #在通配符中表示取反 [^abc] #表示匹配除了abc外的任意一个字符 #在正则表达式中表示以什么开头 ############# # 1.基本语法 # ############# #1. 基本命令 echo $SHELL cat /etc/shells pwd bash --version echo -en <txt> #-n:可以取消末尾的回车符,使得下一个提示符紧跟在输出内容的后面 -e:会解释引号(双引号和单引号)里面的特殊字符(比如换行符\n) type -at <cmd> #用来判断命令的来源 -a:查看一个命令的所有定义 -t:可以返回一个命令的类型 cd - #可以返回前一次的目录 source .bashrc #用于执行一个脚本,通常用于重新加载一个配置文件 source ./lib.sh #在脚本内部加载外部库 alias #可以显示所有别名 alias NAME=DEFINITION #NAME是别名的名称,DEFINITION是别名对应的原始命令。注意,等号两侧不能有空格,否则会报错。 unalias lt #解除别名 awk #AWK 是一种处理文本文件的语言,是一个强大的文本分析工具。 #2. 命令输入格式 #2.1 将长命令用反斜杠(\)分成多行短命令 echo foo bar echo foo \ bar #2.2 分号(;)是命令的结束符,使得一行可以放置多个命令 clear;ls #2.3 命令的组合符&&和||,允许更好地控制多个命令之间的继发关系 Command1 && Command2 #如果Command1命令运行成功,则继续运行Command2命令 Command1 || Command2 #如果Command1命令运行失败,则继续运行Command2命令 ############## # 2.模式扩展 # ############# #Bash 接收到命令以后,发现里面有通配符,会进行通配符扩展,然后再执行命令 #文件名扩展在没有可匹配的文件时,会原样输出 #1. 波浪线扩展 #波浪线~会自动扩展成当前用户的主目录 echo ~ #2. ? 字符扩展 #?字符代表文件路径里面的任意单个字符,不包括空字符 ls ?.txt ls ??.txt #3. * 字符扩展 #*字符代表文件路径里面的任意数量的字符,包括零个字符 #*只匹配当前目录,不会匹配子目录 #*不会匹配隐藏文件 ls *.txt ls * #4.1 方括号扩展 #[aeiou]可以匹配五个元音字母中的任意一个 ls [ab].txt ls ?[!a]? == ls ?[^a]? #4.2 [start-end] 扩展 #方括号扩展有一个简写形式[start-end],表示匹配一个连续的范围,[a-c]等同于[abc],[0-9]匹配[0123456789] ls [a-c].txt ls report[0-9].txt #[!a-zA-Z]表示匹配非英文字母的字符 echo report[!1–3].txt #5.1 大括号扩展 #大括号扩展{...}表示分别扩展成大括号里面的所有值,各个值之间使用逗号分隔 echo d{a,e,i,u,o}g #5.2 {start..end} 扩展 #大括号扩展有一个简写形式{start..end},表示扩展成一个连续序列,比如{a..z}可以扩展成26个小写英文字母 echo {a..c} echo {c..a} mkdir {2007..2009}-{01..12} rm -rf mkdir {2007..2009}-{01..12} echo {001..5} #(start..end..step),用来指定扩展的步长 echo {0..8..2} echo {a..c}{1..3} #6. 变量扩展 #Bash 将美元符号$开头的词元视为变量,将其扩展成变量值 echo $SHELL echo ${SHELL} #${!string*}或${!string@}返回所有匹配给定字符串string的变量名 echo ${!S*} #7. 子命令扩展 #$(...)可以扩展成另一个命令的运行结果,该命令的所有输出都会作为返回值 echo $(date) == echo `date` #8. 算术扩展 #$((...))可以扩展成整数运算的结果 echo $((2 + 2)) #9. 字符类 #[[:class:]]表示一个字符类,扩展成某一类特定字符之中的一个 [[:alnum:]] #匹配任意英文字母与数字 [[:alpha:]] #匹配任意英文字母 [[:blank:]] #空格和 Tab 键。 [[:cntrl:]] #ASCII 码 0-31 的不可打印字符。 [[:digit:]] #匹配任意数字 0-9。 [[:graph:]] #A-Z、a-z、0-9 和标点符号。 [[:lower:]] #匹配任意小写字母 a-z。 [[:print:]] #ASCII 码 32-127 的可打印字符。 [[:punct:]] #标点符号(除了 A-Z、a-z、0-9 的可打印字符)。 [[:space:]] #空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。 [[:upper:]] #匹配任意大写字母 A-Z。 [[:xdigit:]] #16进制字符(A-F、a-f、0-9)。 echo [[:upper:]]* #输出所有大写字母开头的文件名 echo [![:digit:]]* #输出所有不以数字开头的文件名 ############### # 3.引号与转义 # ############### #1. 转义 echo \$date #如果想要原样输出特殊字符,就必须在前面加上反斜杠,使其变成普通字符。这就叫做“转义”(escape) echo \\ #反斜杠本身也是特殊字符,如果想要原样输出反斜杠,就需要对它自身转义,连续使用两个反斜线(\\) #反斜杠除了用于转义,还可以表示一些不可打印的字符 \a:响铃 \b:退格 \n:换行 \r:回车 \t:制表符 echo -e "a\tb" #如果想要在命令行使用这些不可打印的字符,可以把它们放在引号里面,然后使用echo命令的-e参数 #由于反斜杠可以对换行符转义,使得 Bash 认为换行符是一个普通字符,从而可以将一行命令写成多行。 #如果一条命令过长,就可以在行尾使用反斜杠,将其改写成多行。这是常见的多行命令的写法 mv \ /path/to/foo \ /path/to/bar # 等同于 mv /path/to/foo /path/to/bar #2. 单引号 #单引号用于保留字符的字面含义,各种特殊字符在单引号里面,都会变为普通字符,比如星号(*)、美元符号($)、反斜杠(\)等 #单引号使得 Bash 扩展、变量引用、算术运算和子命令,都失效了。如果不使用单引号,它们都会被 Bash 自动扩展。 $ echo '*' * $ echo '$USER' $USER $ echo '$((2+2))' $((2+2)) $ echo '$(echo foo)' $(echo foo) #在双引号之中使用单引号 $ echo "it's" #在双引号之中使用单引号 #3. 双引号 #双引号比单引号宽松,可以保留大部分特殊字符的本来含义,但是三个字符除外:美元符号($)、反引号(` )和反斜杠(\)。也就是说,这三个字符在双引号之中,会被 Bash 自动扩展。 #美元符号和反引号在双引号中,都保持特殊含义。美元符号用来引用变量,反引号则是执行子命令 $ echo "$SHELL" /bin/bash $ echo "`date`" Mon Jan 27 13:33:18 CST 2020 #保存原始命令的输出格式 # 单行输出 $ echo $(cal) 一月 2020 日 一 二 三 四 五 六 1 2 3 ... 31 # 原始格式输出 $ echo "$(cal)" 一月 2020 日 一 二 三 四 五 六 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ########## # 4.变量 # ########## #1. 查看变量 #环境变量是 Bash 环境自带的变量,进入 Shell 时已经定义好了,可以直接使用 env == printenv #可以显示所有环境变量 printenv PATH == echo $PATH #查看单个环境变量的值 #自定义变量是用户在当前 Shell 里面自己定义的变量,必须先定义后使用,而且仅在当前 Shell 可用 set #可以显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数 #2. 创建变量 #Bash 没有数据类型的概念,所有的变量值都是字符串 variable=value #变量声明的语法,等号左边是变量名,右边是变量 注意,等号两边不能有空格 myvar="hello world" #如果变量的值包含空格,则必须将值放在引号中 a=z # 变量 a 赋值为字符串 z b="a string" # 变量值包含空格,就必须放在引号里面 c="a string and $b" # 变量值可以引用其他变量的值 d="\t\ta string\n" # 变量值可以使用转义字符 e=$(ls -l foo.txt) # 变量值可以是命令的执行结果 f=$((5 * 7)) # 变量值可以是数学运算的结果 #3. 读取变量 #读取变量的时候,直接在变量名前加上$就可以了 echo $foo echo ${a}_file #可以用于变量名与其他字符连用的情况 echo ${!myvar} #如果变量的值本身也是变量,可以使用${!varname}的语法,读取最终的值 #4. 输出变量 #为了把变量传递给子 Shell,需要使用export命令 这样输出的变量,对于子 Shell 来说就是环境变量 export NAME=value #用来向子 Shell 输出变量 #子 Shell 如果修改继承的变量,不会影响父 Shell #5. 特殊变量 $? #$?为上一个命令的退出码,用来判断上一个命令是否执行成功。返回值是0,表示上一个命令执行成功;如果是非零,上一个命令执行失败。 $$ #$$为当前 Shell 的进程 ID,这个特殊变量可以用来命名临时文件 $_ #$_为上一个命令的最后一个参数 $! #$!为最近一个后台执行的异步命令的进程 ID $0 #$0为当前 Shell 的名称(在命令行直接执行时)或者脚本名(在脚本中执行时) $- #$-为当前 Shell 的启动参数 $@ $# #$@和$#表示脚本的参数数量 ############### # 5.字符串操作 # ############### ############## # 6.算数运算 # ############# ############ # 7.行操作 # ############ #操作历史 echo $HISTFILE /home/me/.bash_history history #能显示操作历史,即.bash_history文件的内容 export HISTTIMEFORMAT='%F %T ' #通过定制环境变量HISTTIMEFORMAT,可以显示每个操作的时间 history | grep /usr/bin #返回.bash_history文件里面,那些包含/usr/bin的命令 history -c #清除操作历史 ############# # 8.目录堆栈 # ############# ############# # 9.脚本入门 # ############# #1. Shebang 行 # 脚本(script)就是包含一系列命令的一个文本文件。Shell 读取这个文件,依次执行里面的所有命令,就好像这些命令直接输入到命令行一样 # 脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!字符开头,这个字符称为 Shebang,所以这一行就叫做 Shebang 行。 # “#!”后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh或/bin/bash #!/bin/sh #!/bin/bash # 如果 Bash 解释器不放在目录/bin,脚本就无法执行了 #!/usr/bin/env bash #/usr/bin/env bash的意思就是,返回bash可执行文件的位置,前提是bash的路径是在$PATH里面 ./script.sh #有 Shebang 行的时候,可以直接调用执行 bash ./script.sh #如果没有 Shebang 行,就只能手动将脚本传给解释器来执行 #2. 执行权限和路径 #如果将脚本放在环境变量$PATH指定的目录中,就不需要指定路径了。因为 Bash 会自动到这些目录中,寻找是否存在同名的可执行文件 chmod +x script.sh #给所有用户执行权限 chmod +rx script.sh == chmod 755 script.sh #给所有用户读权限和执行权限 chmod u+rx script.sh #只给脚本拥有者读权限和执行权限 export PATH=$PATH:~/bin #建议在主目录新建一个~/bin子目录,专门存放可执行脚本,然后把~/bin加入$PATH env -i /bin/sh #新建一个不带任何环境变量的 Shell #3. 脚本参数 script.sh word1 word2 word $0 #脚本文件名,即script.sh。 $1~$9 #对应脚本的第一个参数到第九个参数。 $# #参数的总数。 $@ #全部的参数,参数之间使用空格分隔。 $* #全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。 echo "全部参数:" $@ echo "命令行参数数量:" $# echo '$0 = ' $0 echo '$1 = ' $1 echo '$2 = ' $2 echo '$3 = ' $3 for i in "$@"; do echo $i done #4. read命令 #!/bin/bash # read-single: read multiple values into default variable echo -n "Enter one or more values > " read echo "REPLY = '$REPLY'" #read可以接受用户输入的多个值 #如果read命令之后没有定义变量名,那么环境变量REPLY会包含所有的输入 #read命令除了读取键盘输入,可以用来读取文件 ############## # 10.条件判断 # ############## #0. 补充 #当${var}为空或未设置时,语句被解释为 if [ == "0" ],出现语法错误。 #加上x后,当${var}为空或未设置时,解释为if [ “x" == "x" ] ,依然正确 #if [ “x${var}" == “x” ] 判断${var}是否为空,添加x是为了防止出现语法错误 #1. if结构 #if是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令 if commands; then commands [elif commands; then commands...] [else commands] fi #if关键字后面是主要的判断条件,elif用来添加在主条件不成立时的其他判断条件,else则是所有条件都不成立时要执行的部分 if test $USER = "foo"; then echo "Hello foo." else echo "You are not foo." fi #if和then写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号 if true then echo 'hello world' fi if false then echo 'it is false' # 本行不会执行 fi #除了多行的写法,if结构也可以写成单行 if true; then echo 'hello world'; fi #if关键字后面也可以是一条命令,该条命令执行成功(返回值0),就意味着判断条件成立 if echo 'hi'; then echo 'hello world'; fi #if后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回0,就会执行then的部分。 if false; true; then echo 'hello world'; fi #elif部分可以有多个 #!/bin/bash echo -n "输入一个1到3之间的数字(包含两端)> " read character if [ "$character" = "1" ]; then echo 1 elif [ "$character" = "2" ]; then echo 2 elif [ "$character" = "3" ]; then echo 3 else echo 输入不符合要求 fi #2. test 命令 # 上面的expression是一个表达式。这个表达式为真,test命令执行成功(返回值为0);表达式为伪,test命令执行失败(返回值为1)。注意,第二种和第三种写法,[和]与内部的表达式之间必须有空格。 # 写法一 if test -e /tmp/foo.txt ; then echo "Found foo.txt" fi # 写法二 if [ -e /tmp/foo.txt ] ; then echo "Found foo.txt" fi # 写法三 if [[ -e /tmp/foo.txt ]] ; then echo "Found foo.txt" fi #3. 判断表达式 #3.1 文件判断 [ -a file ] #如果 file 存在,则为true。 [ -b file ] #如果 file 存在并且是一个块(设备)文件,则为true。 [ -c file ] #如果 file 存在并且是一个字符(设备)文件,则为true。 [ -d file ] #如果 file 存在并且是一个目录,则为true。 [ -e file ] #如果 file 存在,则为true。 [ -f file ] #如果 file 存在并且是一个普通文件,则为true。 [ -g file ] #如果 file 存在并且设置了组 ID,则为true。 [ -G file ] #如果 file 存在并且属于有效的组 ID,则为true。 [ -h file ] #如果 file 存在并且是符号链接,则为true。 [ -k file ] #如果 file 存在并且设置了它的“sticky bit”,则为true。 [ -L file ] #如果 file 存在并且是一个符号链接,则为true。 [ -N file ] #如果 file 存在并且自上次读取后已被修改,则为true。 [ -O file ] #如果 file 存在并且属于有效的用户 ID,则为true。 [ -p file ] #如果 file 存在并且是一个命名管道,则为true。 [ -r file ] #如果 file 存在并且可读(当前用户有可读权限),则为true。 [ -s file ] #如果 file 存在且其长度大于零,则为true。 [ -S file ] #如果 file 存在且是一个网络 socket,则为true。 [ -t fd ] #如果 fd 是一个文件描述符,并且重定向到终端,则为true。 这可以用来判断是否重定向了标准输入/输出错误。 [ -u file ] #如果 file 存在并且设置了 setuid 位,则为true。 [ -w file ] #如果 file 存在并且可写(当前用户拥有可写权限),则为true。 [ -x file ] #如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true。 [ file1 -nt file2 ] #如果 FILE1 比 FILE2 的更新时间最近,或者 FILE1 存在而 FILE2 不存在,则为true。 [ file1 -ot file2 ] #如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true。 [ FILE1 -ef FILE2 ] #如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true。 #上面代码中,$FILE要放在双引号之中。这样可以防止$FILE为空,因为这时[ -e ]会判断为真。而放在双引号之中,返回的就总是一个空字符串,[ -e "" ]会判断为伪。 #!/bin/bash FILE=~/.bashrc if [ -e "$FILE" ]; then if [ -f "$FILE" ]; then echo "$FILE is a regular file." fi if [ -d "$FILE" ]; then echo "$FILE is a directory." fi if [ -r "$FILE" ]; then echo "$FILE is readable." fi if [ -w "$FILE" ]; then echo "$FILE is writable." fi if [ -x "$FILE" ]; then echo "$FILE is executable/searchable." fi else echo "$FILE does not exist" exit 1 fi #3.2 字符串判断 [ string ] #如果string不为空(长度大于0),则判断为真。 [ -n string ] #如果字符串string的长度大于零,则判断为真。 [ -z string ] #如果字符串string的长度为零,则判断为真。 [ string1 = string2 ] #如果string1和string2相同,则判断为真。 [ string1 == string2 ] #等同于[ string1 = string2 ]。 [ string1 != string2 ] #如果string1和string2不相同,则判断为真。 [ string1 '>' string2 ] #如果按照字典顺序string1排列在string2之后,则判断为真。 [ string1 '<' string2 ] #如果按照字典顺序string1排列在string2之前,则判断为真。 #注意,test命令内部的>和<,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。 #!/bin/bash ANSWER=maybe if [ -z "$ANSWER" ]; then echo "There is no answer." >&2 exit 1 fi if [ "$ANSWER" = "yes" ]; then echo "The answer is YES." elif [ "$ANSWER" = "no" ]; then echo "The answer is NO." elif [ "$ANSWER" = "maybe" ]; then echo "The answer is MAYBE." else echo "The answer is UNKNOWN." fi #3.3 整数判断 [ integer1 -eq integer2 ] #如果integer1等于integer2,则为true。 [ integer1 -ne integer2 ] #如果integer1不等于integer2,则为true。 [ integer1 -le integer2 ] #如果integer1小于或等于integer2,则为true。 [ integer1 -lt integer2 ] #如果integer1小于integer2,则为true。 [ integer1 -ge integer2 ] #如果integer1大于或等于integer2,则为true。 [ integer1 -gt integer2 ] #如果integer1大于integer2,则为true。 #!/bin/bash INT=-5 if [ -z "$INT" ]; then echo "INT is empty." >&2 exit 1 fi if [ $INT -eq 0 ]; then echo "INT is zero." else if [ $INT -lt 0 ]; then echo "INT is negative." else echo "INT is positive." fi if [ $((INT % 2)) -eq 0 ]; then echo "INT is even." else echo "INT is odd." fi fi #3.4 正则判断 #[[ expression ]]这种判断形式,支持正则表达式 [[ string1 =~ regex ]] #regex是一个正则表示式,=~是正则比较运算符 #!/bin/bash INT=-5 if [[ "$INT" =~ ^-?[0-9]+$ ]]; then echo "INT is an integer." exit 0 else echo "INT is not an integer." >&2 exit 1 fi #3.5 test 判断的逻辑运算 AND #符号&&,也可使用参数-a OR #符号||,也可使用参数-o NOT #符号! #!/bin/bash MIN_VAL=1 MAX_VAL=100 INT=50 if [[ "$INT" =~ ^-?[0-9]+$ ]]; then if [[ $INT -ge $MIN_VAL && $INT -le $MAX_VAL ]]; then echo "$INT is within $MIN_VAL to $MAX_VAL." else echo "$INT is out of range." fi else echo "INT is not an integer." >&2 exit 1 fi #使用否定操作符!时,最好用圆括号确定转义的范围 #test命令内部使用的圆括号,必须使用引号或者转义,否则会被 Bash 解释 if [ ! \( $INT -ge $MIN_VAL -a $INT -le $MAX_VAL \) ]; then echo "$INT is outside $MIN_VAL to $MAX_VAL." else echo "$INT is in range." fi #3.6 算术判断 #Bash 还提供了((...))作为算术条件,进行算术运算的判断 #如果算术计算的结果是非零值,则表示判断成立。这一点跟命令的返回值正好相反,需要小心 ((1))表示判断成立,((0))表示判断不成立 if ((3 > 2)); then echo "true" fi $ if ((1)); then echo "It is true."; fi It is true. $ if ((0)); then echo "It is true."; else echo "it is false."; fi It is false. #!/bin/bash INT=-5 if [[ "$INT" =~ ^-?[0-9]+$ ]]; then if ((INT == 0)); then echo "INT is zero." else if ((INT < 0)); then echo "INT is negative." else echo "INT is positive." fi if (( ((INT % 2)) == 0)); then echo "INT is even." else echo "INT is odd." fi fi else echo "INT is not an integer." >&2 exit 1 fi #3.7 普通命令的逻辑运算 mkdir temp && cd temp #创建一个名为temp的目录,执行成功后,才会执行第二个命令,进入这个目录 [ -d temp ] || mkdir temp #会测试目录temp是否存在,如果不存在,就会执行第二个命令,创建这个目录。这种写法非常有助于在脚本中处理错误。 if [ condition ] && [ condition ]; then command fi #! /bin/bash filename=$1 word1=$2 word2=$3 if grep $word1 $filename && grep $word2 $filename then echo "$word1 and $word2 are both in $filename." fi #下面的示例演示如何将一个&&判断表达式,改写成对应的if结构。 [[ -d "$dir_name" ]] && cd "$dir_name" && rm * # 等同于 if [[ ! -d "$dir_name" ]]; then echo "No such directory: '$dir_name'" >&2 exit 1 fi if ! cd "$dir_name"; then echo "Cannot cd to '$dir_name'" >&2 exit 1 fi if ! rm *; then echo "File deletion failed. Check results" >&2 exit 1 fi #4. case 结构 #case结构用于多值判断,可以为每个值指定对应的命令,跟包含多个elif的if结构等价,但是语义更好。 case expression in pattern ) commands ;; pattern ) commands ;; ... esac #!/bin/bash echo -n "输入一个1到3之间的数字(包含两端)> " read character case $character in 1 ) echo 1 ;; 2 ) echo 2 ;; 3 ) echo 3 ;; * ) echo 输入不符合要求 esac #判断当前是什么操作系统 #!/bin/bash OS=$(uname -s) case "$OS" in FreeBSD) echo "This is FreeBSD" ;; Darwin) echo "This is Mac OSX" ;; AIX) echo "This is AIX" ;; Minix) echo "This is Minix" ;; Linux) echo "This is Linux" ;; *) echo "Failed to identify this OS" ;; esac #case的匹配模式可以使用各种通配符 a) #匹配a。 a|b) #匹配a或b。 [[:alpha:]]) #匹配单个字母。 ???) #匹配3个字符的单词。 *.txt) #匹配.txt结尾。 *) #匹配任意输入,通过作为case结构的最后一个模式。 #!/bin/bash echo -n "输入一个字母或数字 > " read character case $character in [[:lower:]] | [[:upper:]] ) echo "输入了字母 $character" ;; [0-9] ) echo "输入了数字 $character" ;; * ) echo "输入不符合要求" esac #Bash 4.0之前,case结构只能匹配一个条件,然后就会退出case结构。Bash 4.0之后,允许匹配多个条件,这时可以用;;&终止每个条件块。 #可以看到条件语句结尾添加了;;&以后,在匹配一个条件之后,并没有退出case结构,而是继续判断下一个条件。 #!/bin/bash # test.sh read -n 1 -p "Type a character > " echo case $REPLY in [[:upper:]]) echo "'$REPLY' is upper case." ;;& [[:lower:]]) echo "'$REPLY' is lower case." ;;& [[:alpha:]]) echo "'$REPLY' is alphabetic." ;;& [[:digit:]]) echo "'$REPLY' is a digit." ;;& [[:graph:]]) echo "'$REPLY' is a visible character." ;;& [[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;& [[:space:]]) echo "'$REPLY' is a whitespace character." ;;& [[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;& esac ############ # 11.循环 # ########### #1. while 循环 #while循环有一个判断条件,只要符合条件,就不断循环执行指定的语句。 while condition; do commands done #!/bin/bash number=0 while [ "$number" -lt 10 ]; do echo "Number = $number" number=$((number + 1)) done #关键字do可以跟while不在同一行,这时两者之间不需要使用分号分隔。 while true do echo 'Hi, while looping ...'; done #while循环写成一行,也是可以的。 while true; do echo 'Hi, while looping ...'; done #while的条件部分也可以是执行一个命令。 while echo 'ECHO'; do echo 'Hi, while looping ...'; done #while的条件部分可以执行任意数量的命令,但是执行结果的真伪只看最后一个命令的执行结果。 while true; false; do echo 'Hi, looping ...'; done #2. until 循环 #一般来说,until用得比较少,完全可以统一都使用while。 #until循环与while循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。 until condition; do commands done until condition do commands done #until的部分一直为false,导致命令无限运行,必须按下 Ctrl + c 终止。 until false; do echo 'Hi, until looping ...'; done #只要变量number小于10,就会不断加1,直到number大于等于10,就退出循环。 #!/bin/bash number=0 until [ "$number" -ge 10 ]; do echo "Number = $number" number=$((number + 1)) done #until的条件部分也可以是一个命令,表示在这个命令执行成功之前,不断重复尝试。 #只要cp $1 $2这个命令执行不成功,就5秒钟后再尝试一次,直到成功为止。 until cp $1 $2; do echo 'Attempt to copy failed. waiting...' sleep 5 done #until循环都可以转为while循环,只要把条件设为否定即可。上面这个例子可以改写如下。 while ! cp $1 $2; do echo 'Attempt to copy failed. waiting...' sleep 5 done #3. for…in 循环 #for...in循环用于遍历列表的每一项。 for variable in list do commands done #关键词do可以跟for写在同一行,两者使用分号分隔。 for variable in list; do commands done #!/bin/bash for i in word1 word2 word3; do echo $i done #列表可以由通配符产生。 for i in *.png; do ls -l $i done #列表也可以通过子命令产生。 #cat ~/.bash_profile命令会输出~/.bash_profile文件的内容,然后通过遍历每一个词,计算该文件一共包含多少个词,以及每个词有多少个字符。 #!/bin/bash count=0 for i in $(cat ~/.bash_profile); do count=$((count + 1)) echo "Word $count ($i) contains $(echo -n $i | wc -c) characters" done #in list的部分可以省略,这时list默认等于脚本的所有参数$@。 for filename; do echo "$filename" done # 等同于 for filename in "$@" ; do echo "$filename" done #4. for 循环 #for循环还支持 C 语言的循环语法。 #expression1用来初始化循环条件,expression2用来决定循环结束的条件,expression3在每次循环迭代的末尾执行,用于更新值。 #注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号$。 for (( expression1; expression2; expression3 )); do commands done # 等同于 (( expression1 )) while (( expression2 )); do commands (( expression3 )) done for (( i=0; i<5; i=i+1 )); do echo $i done #for条件部分的三个语句,都可以省略。 #脚本会反复读取命令行输入,直到用户输入了一个点(.)位为止,才会跳出循环。 for ((;;)) do read var if [ "$var" = "." ]; then break fi done #5. break,continue #break命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。 #!/bin/bash for number in 1 2 3 4 5 6 do echo "number is $number" if [ "$number" = "3" ]; then break fi done #continue命令立即终止本轮循环,开始执行下一轮循环。 #!/bin/bash while read -p "What file do you want to test?" filename do if [ ! -e "$filename" ]; then echo "The file does not exist." continue fi echo "You entered a valid file.." done #6. select 结构 #select结构主要用来生成简单的菜单。它的语法与for...in循环基本一致。 select name [in list] do commands done #Bash 会对select依次进行下面的处理。 #1. select生成一个菜单,内容是列表list的每一项,并且每一项前面还有一个数字编号。 #2. Bash 提示用户选择一项,输入它的编号。 #3. 用户输入以后,Bash 会将该项的内容存在变量name,该项的编号存入环境变量REPLY。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。 #4. 执行命令体commands。 #5. 执行结束后,回到第一步,重复这个过程。 #!/bin/bash # select.sh select brand in Samsung Sony iphone symphony Walton do echo "You have chosen $brand" done #select可以与case结合,针对不同项,执行不同的命令。 #!/bin/bash echo "Which Operating System do you like?" select os in Ubuntu LinuxMint Windows8 Windows7 WindowsXP do case $os in "Ubuntu"|"LinuxMint") echo "I also use $os." ;; "Windows8" | "Windows10" | "WindowsXP") echo "Why don't you try Linux?" ;; *) echo "Invalid entry." break ;; esac done ########### # 12.函数 # ########### #1. 简介 #函数(function)是可以重复使用的代码片段,有利于代码的复用。它与别名(alias)的区别是,别名只适合封装简单的单个命令,函数则可以封装复杂的多行命令。 #函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。如果函数与脚本同名,函数会优先执行。但是,函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。 # 第一种 fn() { # codes } # 第二种 function fn() { # codes } #一个简单函数的例子。 hello() { echo "Hello $1" } #一个多行函数的例子,显示当前日期时间 today() { echo -n "Today's date is: " date +"%A, %B %-d, %Y" } #删除一个函数,可以使用unset命令。 unset -f functionName #查看当前 Shell 已经定义的所有函数,可以使用declare命令。 declare命令不仅会输出函数名,还会输出所有定义。输出顺序是按照函数名的字母表顺序。由于会输出很多内容,最好通过管道命令配合more或less使用。 declare -f #declare命令还支持查看单个函数的定义。 declare -f functionName #declare -F可以输出所有已经定义的函数名,不含函数体。 declare -F #2. 参数变量 #函数体内可以使用参数变量,获取函数参数。函数的参数变量,与脚本参数变量是一致的 $1~$9 #函数的第一个到第9个的参数。 $0 #函数所在的脚本名。 $# #函数的参数总数。 $@ #函数的全部参数,参数之间使用空格分隔。 $* #函数的全部参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。 #!/bin/bash # test.sh function alice { echo "alice: $@" echo "$0: $1 $2 $3 $4" echo "$# arguments" } alice in wonderland #日志函数 function log_msg { echo "[`date '+ %F %T'` ]: $@" } #3. return 命令 #return命令用于从函数返回一个值。函数执行到这条命令,就不再往下执行了,直接返回了。 function func_return_value { return 10 } #return后面不跟参数,只用于返回也是可以的。 function name { commands return } #4. 全局变量和局部变量,local 命令 #Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。这一点需要特别小心。 #变量$foo是在函数fn内部声明的,函数体外也可以读取。 # 脚本 test.sh fn () { foo=1 echo "fn: foo = $foo" } fn echo "global: foo = $foo" #函数体内不仅可以声明全局变量,还可以修改全局变量。 foo=1 fn () { foo=2 } echo $foov #函数里面可以用local命令声明局部变量。 #local命令声明的$foo变量,只在函数体内有效,函数体外没有定义。 # 脚本 test.sh fn () { local foo foo=1 echo "fn: foo = $foo" } fn echo "global: foo = $foo" ########### # 13.数组 # ########### ################ # 14.正则表达式 # ################ ################## # 15.处理文本文件 # ################## #1. awk命令 #1.1 基本用法 # 格式 awk 动作 文件名 # 示例 # $ + 数字表示某个字段 awk '{print $0}' demo.txt #demo.txt是awk所要处理的文本文件。前面单引号内部有一个大括号,里面就是每一行的处理动作print $0。其中,print是打印命令,$0代表当前行,因此上面命令的执行结果,就是把每一行原样打印出来。 echo 'this is a test' | awk '{print $0}' #rint $0就是把标准输入this is a test,重新打印了一遍 # awk会根据空格和制表符,将每一行分成若干字段,依次用$1、$2、$3代表第一个字段、第二个字段、第三个字段等等。 echo 'this is a test' | awk '{print $3}' #$3代表this is a test的第三个字段a awk -F ':' '{ print $1 }' demo.txt #文件的字段分隔符是冒号(:),所以要用-F参数指定分隔符为冒号。然后,才能提取到它的第一个字段 #1.2 变量 echo 'this is a test' | awk '{print $NF}' #变量NF表示当前行有多少个字段,因此$NF就代表最后一个字段 awk -F ':' '{print $1, $(NF-1)}' demo.txt #$(NF-1)代表倒数第二个字段,print命令里面的逗号,表示输出的时候,两个部分之间使用空格分隔 awk -F ':' '{print NR ") " $1}' demo.txt #变量NR表示当前处理的是第几行,print命令里面,如果原样输出字符,要放在双引号里面 FILENAME #当前文件名 FS #字段分隔符,默认是空格和制表符。 RS #行分隔符,用于分割每一行,默认是换行符。 OFS #输出字段的分隔符,用于打印时分隔字段,默认为空格。 ORS #输出记录的分隔符,用于打印时分隔记录,默认为换行符。 OFMT #数字输出的格式,默认为%.6g。 #1.3 函数 awk -F ':' '{ print toupper($1) }' demo.txt #函数toupper()用于将字符转为大写 #tolower() #字符转为小写。 #length() #返回字符串长度。 #substr() #返回子字符串。 #sin() #正弦。 #cos() #余弦。 #sqrt() #平方根。 #rand() #随机数。 #1.4 条件 awk '条件 动作' 文件名 #awk允许指定输出条件,只输出符合条件的行。输出条件要写在动作的前面 awk -F ':' '/usr/ {print $1}' demo.txt #print命令前面是一个正则表达式,只输出包含usr的行 awk -F ':' 'NR % 2 == 1 {print $1}' demo.txt #只输出奇数行 awk -F ':' 'NR >3 {print $1}' demo.txt #输出第三行以后的行 awk -F ':' '$1 == "root" {print $1}' demo.txt #输出第一个字段等于指定值的行 awk -F ':' '$1 == "root" || $1 == "bin" {print $1}' demo.txt #输出第一个字段等于指定值的行 #1.5 if语句 awk -F ':' '{if ($1 > "m") print $1}' demo.txt #输出第一个字段的第一个字符大于m的行 awk -F ':' '{if ($1 > "m") print $1; else print "---"}' demo.txt #2. sed命令 #sed 把每一行都存在临时缓存区中,对这个副本进行编辑,所以不会修改或破坏源文件 sed [option] 'command' <inputfile> #2.1 正则表达式 #2.2 常用选项 -n #使用安静模式,在一般情况所有的 STDIN 都会输出到屏幕上,加入-n 后只打印被 sed 特殊处理的行 -e #多重编辑,且命令顺序会影响结果 -f #指定一个 sed 脚本文件到命令行执行, -r #Sed 使用扩展正则 -i #直接修改文档读取的内容,不在屏幕上输出 #2.3 操作命令 #sed 操作命令告诉 sed 如何处理由地址指定的各输入行。如果没有指定地址, sed 就会处理输入的所有的行。 a\ #在当前行后添加一行或多行 c\ #用新文本修改(替换)当前行中的文本 d #删除行 i\ #在当前行之前插入文本 h #把模式空间里的内容复制到暂存缓存区 H #把模式空间里的内容追加到暂存缓存区 g #取出暂存缓冲区里的内容,将其复制到模式空间,覆盖该处原有内容 G #取出暂存缓冲区里的内容,将其复制到模式空间,追加在原有内容后面 l #列出非打印字符 p #打印行 n #读入下一输入行,并从下一条命令而不是第一条命令开始处理 q #结束或退出 sed r #从文件中读取输入行 ! #对所选行意外的所有行应用命令 s #用一个字符串替换另一个 #2.4 替换标志 g #在行内进行全局替换 p #打印行 w #将行写入文件 x #交换暂存缓冲区与模式空间的内容 y #将字符转换为另一字符(不能对正则表达式使用 y 命令) #3. 向文件中添加内容 #3.1 vi编辑法 #打开终端,输入vi test.txt 回车,按a或i进入编辑模式,输入 I am a boy,然后按esc键退出编辑模式,输入:wq保存并退出 #3.2 echo命令法 #打开终端,输入 echo ‘I am a boy’ >> ./test.txt #追加单行文本法 #3.3 cat命令法 #注:结尾的EOF要顶格,成对出现,可以其它字符代替 cat >> ./test.txt <<EOF I am a boy EOF #3.4 cat编辑法 cat >> ./test.txt #回车后开始编辑输入内容 I am a boy. #按cntl+d组合键结束编辑

     

    Processed: 0.009, SQL: 9