1.1简介
命令提示符中,‘$’表示普通用户,’#’表示超级用户。shell脚本通常是以“#!”其实的文本文件。其中“#!”称为shebang,在执行脚本时,shell程序会读取脚本的首行,查看shebang行是否为“#! /bin/bash”。它会识别/bin/bash,并在内部以如下命令执行
当打开一个终端时,该终端就会执行一遍~/.bashrc。在bash中,每个命令或命令序列是通过使用换行符来分隔的。
1.2终端打印
如下三种都可以完成同样的输出结果,echo在每次调用后会输出一个换行符(使用 -n 可以忽略掉此换行符):
1 2 |
echo "Welcome to Bash" echo Welcome to Bash |
但各有一些特殊的用途和副作用。
比如双引号中存在‘!’,就必须对其转义,单引号中的$var或被当作常量打印,不带引号的中间不能使用‘;’等等。
另一个可用于终端打印的事printf,使用方法类似于c语言中的printf函数
注意:echo和printf中的标志(-e,-n等)必须出现在命令行内任何字符串之前,如:
补充:
(1)使用双引号内的转义序列:
1 |
#echo -e “包含转义序列的字符串” |
(2)打印彩色:
1 2 3 |
#彩色文本 echo -e "\e[1;31m This is red text \e[0m" #彩色背景 |
1.3变量与环境变量
脚本语言通常不需要在使用之前声明其类型,并且在Bash中,无论你给变量赋值时是否加引号,每一个变量的值都是字符串。环境变量是一类特殊的变量,它用于Shell环境和操作系统环境存储一些特殊的值。
env命令查看所有与此终端进程有关的环境变量。每个进程运行时的环境变量使用下面命令查询:
1 2 3 |
#其中$PID是相应的进程PID,使用pgrep 程序名 cat /proc/$PID/environ #将上述输出重新格式化: |
变量的赋值:
1 |
var=value#赋值 |
打印:
1 2 |
echo $var #或 |
在双引号中引用变量值:
1 2 3 |
#! /bin/bash fruit=apple count=5 |
输出如下:
We have 5 apple(s)
环境变量是未在当前进程中定义,而从父进程中继承而来的变量。export命令用来设置环境变量。
1 2 3 |
export PATH="$PATH:/home/user/bin" #或 PATH=“$PATH:/home/user/bin” |
补充:
(1)获得字符串长度:
1 |
var=1234567890987654321 |
(2)识别当前shell版本
1 2 |
echo $SHELL #或 |
(3)检查是否为超级用户
1 2 3 4 |
if [ $UID -ne 0 ]; then echo Non root user.Please run as root. else echo Root user. |
(4)修改Bash提示字符串
1 2 3 |
#列出设置PS1哪一行 cat ~/.bashrc | grep PS1 #设置提示字符串 |
\u用户名,\hz主机名,\w工作目录
1.4通过shell进行数学运算
在Bash shell中可以利用let、(())、[]进行基本的算数操作,使用expr和bc进行一些高级操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
no1=4 no2=5 #使用let时,变量名之前不需要添加$ let result=no1+no2 echo $result let no1++#自加 let no1--#自减 #简写形式 let no+=6 let no-=6 result=$[ no1+no2 ] result=$[ $no1+5 ] result=$(( no1+no2)) result=`expr 3+4`#"`"是键盘上ESC下面的键 |
以上这些不支持浮点运算。下面开始浮点运算:
1 2 3 |
echo "4*0.56" | bc no=50; result=`echo "$no * 1.5"|bc` |
补充:
(1)设置小数精度
(2)进制转换
1 2 3 4 |
#! /bin/bash no=100 echo "obase=2;$no" | bc no=1100100 |
(3)平方与平方根
1 |
echo "sqrt(100)" | bc#Square root |
1.5文件描述符与重定向
将输出重定向到一个文件中
1 |
echo "This is a sample text 1" > temp.txt#清空 |
当一个命令发生错误并退出时,会返回一个非0的退出状态,退出状态可以通过 echo $?查看
1 2 3 4 |
ls + 2> out.txt#正常运行 ls 2>stderr.txt 1>stdout.txt#将标准错误与标准输出分别重定向到不同文件 ls 2>&1 out.txt #或 |
如果不想在终端显示stderr的任何内容,可以使用/dev/null,/deb/null是一个特殊的设备文件,这个文件接收的任何文件都会被丢弃,null文件通常被称为位桶(bit bucket)或黑洞。
下面的代码中,tee命令接收来自stdin的数据。它将stdout的一份副本写入文件out.txt,同时将另一份副本作为后续命名的stdin。命令cat -n将从stdin中接收到的每一行数据前加上行号并写入stdout(注意tee只能从stdin中进行读取,不能读取stderr):
1 |
cat xxx.txt | tee out.txt | cat -n#覆盖 |
我们可以使用stdin作为命令参数,只需要使用-作为命令的文件名参数即可:
类似的,我们可以使用/dev/stdin作为输出文件名来使用stdin
补充:
(1)将文件重定向到命令
(2)重定向脚本内部的文本块
向log文件中写入头信息:
1 2 3 4 |
#! /bin/bash cat <<EOF>log.txt LOG FILE HEADER This is a test log file |
之后,cat <<EOF>log.txt到下一个EOF中间的内容将作为stdin数据。
(3)自定义文件描述符
我们可以这样使用它:
1 2 3 |
echo this is a test line > test.txt exec 3<input.txt #现在就可以使用文件描述符3了 |
创建一个文件描述符用于追加:
1.6数组和关联数组
Bash支持普通数组和关联数组,普通数组只能使用整数作为数组索引,而关联数组(Bash4.0开始支持)可以使用字符串作为数组索引。
定义数组:
1 2 3 4 5 6 7 8 |
#方法一 array_var=(1 2 3 4 5 6) #方法二 array_var[0]="test1" array_var[1]="test2" array_var[2]="test3" array_var[3]="test4" array_var[4]="test5" |
打印数组元素:
1 2 |
echo ${array_var[0]} index=5 |
打印数组中的所有值
1 2 |
echo ${array_var[*]} #或 |
打印数组长度
补充:
(1)定义关联数组
1 2 3 4 5 6 |
#声明 declare -A ass_array #内嵌索引-值 ass_array=([index1]=var1 [index2]=var2) #独立索引-值 ass_array[index1]=var1 |
(2)列出数组索引
1 2 |
echo ${!array_var[*]} #或 |
1.7使用别名
创建一个别名
1 |
alias new_command='command sequence' |
为了使别名一直起作用,可以将它放入~/.bashrc中。
删除alias:删除~/.bashrc中对应语句或使用unalias
另一种创建别名的方法是定义一个具有新名称的函数,并把它写入 ~/.bashrc
1 |
#删除前先备份 |
补充:
有时我们不希望使用别名,通过字符’\’对命令进行转义,这就使得我们可以执行用来的命令,而不是别名替身。
1.8获取终端信息
tput和stty事两款终端处理工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#获取终端的行数和列数 tput cols tput lines #打印终端名 tput longname #将光标移动到(100,100)处 tput cup 100 100 #设置终端背景色 tput setb no#no在0~7之间 #设置文本前景色 tput serf no#no在0~7之间 #设置文本为粗体 tput bold #设置下划线的起止 tput smu1 tput rmu1 #删除当前光标位置到行尾的所有内容 |
输入密码时,不让输入的内容显示出来
1 2 3 4 5 6 |
#! /bin/bash echo -e "Enter password:" stty -echo#禁止将输出发送到终端 read password stty echo#允许发送输出 echo |
1.9获取设置日期和延时
1 2 3 |
#读取日期 data #打印纪元时 |
纪元时被定义为从世界标准时间1970年1月1日0时0分0秒到现在时间的总秒数,不包括润秒。
将日期串转化为纪元时
1 2 3 |
date --date "Thu Nov 18 08:07:21 IST 2010" +%s #选项--date用于提供日期串作为输入 #获取给定日期是星期几 |
日期格式字符串列表:
星期:%a(Sta),%A(Staurday)
月:%b(Nov).%B(November)
日:%d(31)
固定格式日期:%D(10/18/10)
年:%y(10),%Y(2010)
小时:%I,%H(08)
分钟:%M(33)
秒:%S(10)
纳秒:%N(695208515)
UNIX纪元时(秒为单位):%s(1290049486)
1 2 3 4 |
#用格式串结合+作为date的参数,可以得到对应格式的日期 date "+%d %B %Y" #设置日期时间 date -s "格式化的日期时间字符串" |
检查一组命令所花费的时间
1 2 3 4 5 6 |
#! /bin/bash start=$(date +%s) command; statement; end=$(date +%s) difference=$((end-start)) |
补充:
在脚本中延时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sleep no_of_seconds #下面的脚本使用tput和sleep从0开始计数到40 echo -n Count: tput sc#存储光标位置 count=0; while true; do if [ $count -lt 40 ]; then let count++; sleep 1; tput rc#恢复光标位置 tput ed echo -n $count; else exit 0; fi |
1.10调试脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#使用-x启动跟踪调试脚本 bash -x script.sh #只关心脚本中某部分 #set -x:在执行时显示参数和命令 #set +x:禁止调试 #set -v:当命令进行读取时显示输入 #set +v:禁止打印输入 for i in {1..6} do set -x echo $i set +x done |
以自定义格式显示调试信息_DEBUG
1 2 3 4 5 6 7 8 9 10 11 |
function DEBUG() { [ "$_DEBUG" == "on" ] && $@ ||: } for i in {1..10} do DEBUG echo $i done #可以将调试功能置为“no”来运行上面的脚本 |
补充:
用shebang进行调试:
1 2 3 |
#将 #! /bin/bash #改为 |
1.11函数和参数
定义函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function fname() { statement; } #或 fname() { statement; } #函数调用:直接使用函数名 fname; #函数传参: fname arg1 arg2 #函数访问参数 fname() { echo $1 $2;#访问参数1和参数2,$n代表第n个参数 echo "$@"#以列表的形式一次性打印所有参数,如“$1”,“$2” ehoc "$*";#类似$@,但是参数被作为单个实体,如“$1$2” return 0;#返回值 |
补充:
(1)递归函数
1 2 3 4 5 |
F() { echo $1; F hello; sleep 1; |
(2)导出函数
(3)读取命令返回值
1 2 3 4 5 6 7 8 9 |
#判断命令是否成功结束 CMD="command" # command 指代你要检测的命令 status $CMD if [ $? -eq 0 ]; then echo "$CMD executed successfully" else echo "$CMD executed unsuccessfully" |
(4)向命令传递参数
假设-p、-v是可用选项,-k NO事另一个可以接受数字的选项,同时该命令还接受一个文件名作为参数,那么,它有以下几种执行方式:
1 2 3 |
command -p -v -k 1 file command -pv -k 1 file command -pvk 1 file |
1.12读取命令序列输出
1 2 3 4 |
#ls的输出(当前目录的内容列表)被传递给cat -n,cat -n为通过stdin所接收到输入内容加上行号 ls | cat -n > out.txt #用下面的序列读取命令序列输出: cmd_output=$(ls | cat -n) |
另一种被称为子shell(subshell)的方法也可以用于存储命令输出
1 |
cmd_output=`ls |cat -n` |
反引用‘`’位于键盘的~键上
补充:
(1)利用子shell生成一个独立的进程
子shell本身就是独立的进程,使用()操作符来定义一个子shell
1 2 |
pwd; (cd /bin; ls;) |
在子shel中执行时,不会对当前shell有任何影响
(2)通过引用子shell的方式保留空格和换行符
假设我们使用子shell或反引用的方法将命令的输出读入一个变量中,可以将它放入双引号中,以保留空格和换行符(\n)
1 |
out="$(cat tex.txt)" |
1.13以不按回车键的方式读取字符“n”
read命令提供了一种不需要按回车键就能够搞定这个任务的方法。
1 2 3 4 5 6 7 8 9 10 |
#读入个字符并存入var read -n 2 var echo $var #不回显的方式读取密码 read -s var #显示提示信息 read -p "Enter input:" var #在2秒内将键入的字符串读入变量var read -t 2 var #用:作为定界符结束输入 |
1.14字段分隔符和迭代器
内部字段分隔符(Internal Field Separator,IFS)
CSV(Comma Separator Value),逗号分隔型数值
1 2 3 4 5 6 7 8 |
data="name,sex,rollno,location" #我们可以使用IFS读取变量中的每一个条目 oldIFS=$IFS IFS=,now, for item in $data; do echo Item: $item done |
IFS的默认值为空白符(换行符、制表符或空格)
/etc/password中,每一行包含了由冒号划分的多个条目,类似如下形式:
root:x:0:0:root:/root:/bin/bash
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#打印用户名及其使用的默认shell #!/bin/bash line="root:x:0:0:root:/root:/bin/bash" oldIFS=IFS; IFS=":"; count=0 for item in $line; do [ $count -eq 0 ] && user=$item; [ $count -eq 6 ] && shell=$item; let count++ done; IFS=$oldIFS |
bash中的循环:
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 |
#for循环 for var in list; do command; done #echo {1..50} #echo {a..z} #echo {A..Z} for i in {a..z};do command;done; #for的另一种形式 for((i=0;i<10;i++)) { commands; } #while循环 while condition do commands; done #until循环 x=0; unitl [ $x -eq 9 ]; do let x++; echo $x; |
1.15 比较与测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#if,if else if condition; then commands; fi if condition then commands; elif condition; then commands else commands fi #简洁 [ condition ] && action;#如果condition为真,则执行action [ condition ] || action;#如果condition为假,则执行action |
1 |
[ $var -eq 0 ] |
-gt:大于
-lt:小于
-ge:大于或等于
-le:小于或等于
1 2 |
#结合多个条件进行测试: [ $var1 -ne 0 -a $var2 -gt 2 ]#使用逻辑与 -a |
文件系统相关测试:
[ -f $file_var ]:file_var是否包含正常的文件路径或文件名
[ -x $var ]:var包含的文件是否可执行
[ -d $var ]:是否是目录
[ -e $var ]:文件是否存在
[ -c $var ]:是否是字符设备文件路径
[ -b $var ]:是否是块设备文件路径
[ -w $var ]:是否可写
[ -r $var ]:是否可读
[ -L $var ]:是否是一个符号链接
字符串比较
进行字符串比较时,最好使用双中括号。
[[ $str1 = $str2 ]]:是否相等,注意“=”两边的空格
[[ $str1 == $str2 ]]:是否相等的另一种写法
[[ $str1 != $str2 ]]:是否不等
[[ $str1 > $str2 ]]:str1的字母序大于str2
[[ $str1 < $str2 ]]:str1的字母序小于str2
[[ -z $str1 ]]:是否为空
[[ -n $str2 ]]:是否非空
0 条评论