Skip to content

Latest commit

 

History

History
734 lines (483 loc) · 20.5 KB

1.5_20170213_bash编程、管道和重定向以及grep命令.md

File metadata and controls

734 lines (483 loc) · 20.5 KB

bash编程、管道和重定向以及正则表达式

本节主要讲述了bash的输入输出重定向和管道,以及正则表达式和grep的使用,还有bash的算术运算和条件判断的整数和字符测试,交互式脚本的写法。

bash特性之输入输出重定向和管道

在计算机的IO设备中,都有寄存器,用来存放数据,用来和cpu进行通信

在内核中每打开一个文件,内核也要对文件进行追踪,在linux中这称为文件描述符(file descriptor),在windows中称为文件句柄,打开文件也有上限,分为系统上限和用户上限

文件描述符在形式上是一个非负整数,实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录数,当程序打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符

输入和输出

标准输入stdin,文件描述符用0表示,通常为键盘

标准输出stdout,文件描述符用1表示,通常为显示器

标准错误输出stderr,文件描述符用2表示,通常为显示器

IO重定向

修改数据流的输入输出,是IO重定向

输入重定向<

输入重定向使用<<或<,<是输入重定向,<<此处创建文件,也用于在脚本中创建文件或生成菜单

使用EOF文档结束符可以将EOF之前内容输入重定向

cat > /tmp/filemenu << EOF

a 123

b 234

EOF

输出重定向>

输出重定向使用>>或>,>是覆盖输出,>>是追加输出

使用set -C可以禁用覆盖重定向至已存在的文件,相反使用 set +C可以关闭禁止覆盖重定向至已存在的文件

在-C特性下,使用>|可以强制覆盖重定向

在内核中有一个特殊的设备/dev/null,所有发往此设备的数据流都将被丢弃

错误重定向

错误输出重定向使用2>或者2>>,2>是覆盖错误输出重定向,2>>追加错误输出重定向

同时重定向标准输出和错误输出:COMMAND > /path/to/outfile 2> /path/to/errfile

也可以使用COMMAND &> /path/to/somefile定向至同一个文件,无论执行成功还是失败都指向这个文件,也可以使用COMMAND > /path/to/somefile 2>&1,&1中1表示文件描述符,1是标准输出,目前的标准输出就是/path/to/somefile

管道

管道可以将命令组合,完成更复杂的任务,管道可以将第一个命令的输出作为第二个命令的输入,管道可以连接多个命令,在管道中,最后一个命令一般是在当前shell中子shell中运行,所以不要试图传递变量

使用tee 重定向的文件,既可以将数据流重定向至文件,同时也可以将数据流输出至标准输出,一般将tee命令写在重定向文件的前面

bash的算术运算

bash默认为字符型,可以使用declare声明并初始化变量,-i表示整形变量,-x指定环境变量

let varname=算术表达式,如果计算结果中存在 小数,将会被园整,即小数部分被忽略

varname=$[ 算术表达式 ],注意表达式前后要有空格

varname=$((算术表达式)),仅使用(())没有返回值,使用$(())有返回值

varname=`expr 变量1 + 变量2`,加号的前后都有空格

操作符有+,-,*,/,%

赋值计算中的其他符号

自加并赋值+=

a=12;let a+=2

类似的还有-=,*=,/=,%=

++和+=1的功能大体相同

bash -x 脚本,可以对脚本进行调试,显示脚本的执行过程

脚本的位置参数

$0实际上不是位置参数,指的是脚本自身

$1指的是脚本的第一个参数,

特殊变量

$#:位置参数的个数

$*或$@:引用所有的位置参数

交互式脚本

read命令可以在交互式脚本中,等待用户输入,然后将用户的输入赋值给相应的变量,使用read -p “文本” 变量,可以输出提示,并等待用户输入

-t 时间,设定超时时间,超过时间使用默认值

如果用户在输入过程中,输入错误,按住ctrl键,然后删除

给变量赋予默认值

varname=${varname:-value}

如果varname非空,则其值保持不变;否则varname会使用value作为其值

${varname:-value},如果varname不空则返回varname的值,否则返回value的值,但是没有进行赋值

当使用read获取用户输入时,当用户输入参数个数小于给定的参数个数,则多出的给定参数值为空,当用户输入参数的个数多于给定的参数个数,则一对一进行赋值,最后一个参数接收全部多余的参数

正则表达式

正则表达式是一类字符所书写出的模式(pattern)

正则表达式使用元字符来书写模式,元字符是一些不表示字符本身的意义,而是用于额外功能性的描述的字符

正则表达式分为基本正则表达式和扩展正则表达式

基本正则表达式

字符匹配

元字符 作用
. 匹配任意单个字符
[] 指定范围的任意单个字符
[^] 指定范围外的任意单个字符

常见的字符集有

[0-9][:digit:][a-z][:lower:]等,参见文件名通配

次数匹配

用来指定匹配其前面的字符的次数

元字符 作用
* 任意次
.* 匹配任意长度的任意字符
? 匹配其前面的字符0次或1次
\{m\} 匹配m次
\{m,n\} 至少m次,至多n次
\{m,\} 至少m次
\{0,n\} 至多n次

正则表达式默认为贪婪模式,尽可能长的去匹配字符

位置锚定

用于指定字符出现的位置

  • 行首尾位置锚定

    ^用于锚定行首,$用于锚定行尾,^$用于查找空白行

  • 单词的位置锚定

    \<char锚定词首,也可以使用\bchar

    char\>锚定词尾,也可以使用char\b

分组

使用\(模式\),可以进行分组,当做一个整体对待

引用

对分组的字符串基于位置进行引用

\1:后向引用,引用前面的第一个左括号以及与之对应的右括号中的模式所匹配到的内容,类似的还有\2,\3

扩展正则表达式

字符匹配

元字符 作用
. 匹配任意单个字符
[] 指定范围的任意单个字符
[^] 指定范围外的任意单个字符

次数匹配

用来指定匹配其前面的字符的次数

元字符 作用
* 任意次
.* 匹配任意长度的任意字符
? 匹配其前面的字符0次或1次
{m} 匹配m次
{m,n} 至少m次,至多n次
{m,} 至少m次
{0,n} 至多n次
+ 匹配其前面字符至少一次

位置锚定

用于指定字符出现的位置

  • 行首尾位置锚定

    ^用于锚定行首,$用于锚定行尾,^$用于查找空白行

  • 单词的位置锚定

    \<char锚定词首,也可以使用\bchar

    char\>锚定词尾,也可以使用char\b

分组

使用\(模式\),可以进行分组,当做一个整体对待

或者

使用|表示或者,a|b 表示ab有一个就可以,ac|bc表示ac或者bc,如果想到一个字母使用或者,要使用分组ab(x|y)c

文本处理工具grep、egrep和fgrep

grep是一个根据用户指定的文本模式对目标文件进项逐行搜索,并显示能够被模式所匹配的行的文本搜索工具

一般格式为grep [options] 'pattern' file...,模式中如果有元字符要使用单双引号,如果要对变量进行替换只能使用双引号

默认只支持基本正则表达式

选项 作用
--color=auto 指定颜色显示匹配结果
-v 反向匹配,显示不能被模式匹配的行
-o 仅匹配被模式匹配到的字符串,而非整行
-i 不区分字符大小写
-E 使用扩展的正则表达式
-A 数字 除了显示匹配到的行,还显示下面的一行
-B 数字 除了显示匹配到的行,还显示上面的一行
-C 数字 除了显示匹配到的行,还显示其上下各一行

egrep使用扩展正则表达式来构建模式,相当于grep -E

fgrep不解析正则表达式

bash编程之条件判断

用来判断后续操作的前提条件是否满足。

条件判断的常用测试类型有整数测试,字符测试,文件测试

布尔值

逻辑运算中有与运算,或运算和非运算等

与运算中,真&&真=真;有假得假

或运算中,假||假=假;有真得真

非运算中,!真=假;!假=真

在bash中如何做测试?

test 比较表达式

[ 测试表达式 ],注意表达式两端必须有空格

[[ 测试表达式 ]]

bash条件判断的语法结构

使用if语句进行条件判断

单分支:

if 条件:

​ 分支一;

fi

双分支:

if 条件;then

​ 分支一;

else

​ 分支二;

fi

多分支

if 条件1;then

​ 分支一;

elif 条件2;then

​ 分支二;

elif 条件3;then

​ 分支三;

else

​ 分支n;

fi

只要命令用作条件,就表示引用其状态结果(即执行成功与否),而非命令的输出结果,因此,不能使用命令替换符(反引号或者$)

引用命令的执行结果,使用`COMMAND`或$(COMMAND),引用命令执行成功与否的状态结果,一定是直接执行命令,此时,通常需要将执行结果重定向至 &> /dev/null

整数测试

整数测试通常是二元测试,一般格式为num1 操作符 num2

操作符 作用
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
-eq 等于
-ne 不等于

脚本自定义退出

使用exit 自定义数值

默认情况下,脚本的退出状态取决于最后一个命令的执行结果

0表示正确执行,系统预留的数字有0,1,127,255

bash组合条件测试

组合条件测试是对条件做逻辑运算

与运算:条件1&&条件2,第一个条件为假,则结果为假,因此条件2不执行

或运算:条件1&&条件2,第一个条件为真,则结果为真,因此条件2不执行

非运算:!条件

条件1&&条件2||条件3 表示如果条件1成立,那么进行条件2,否则进行条件3

与的优先级高于或,或的优先级大于非

字符测试

常用的双目测试操作符

操作符 作用
> 大于
< 小于
== 等于,等值比较
=~ 左侧是字符串或变量,右侧是模式,判定左侧的字符串能否被右侧的模式所匹配,通常只在[[]]中使用

模式中可以仍然可以使用行首行尾锚定,但模式不要加引号

常用的单目测试操作符

操作符 作用
-n $string 字符串是否不空,不空为真,空则为假
-z $string 字符串是否为空,空则为真,不空为假

字符测试中,使用变量最好使用双引号

文件测试

文件测试大部分是单目测试

-e(a) file:      文件是否存在		
-f path/to/file:   文件是否为普通文件
-d file:           是否为目录文件
-b somefile :      测试文件是否存在并且是否为一个块设备文件
-c somefile :      测试文件是否存在并且是否为一个字符设备文件
-h(L) somefile : 测试文件是否存在并且是否为符号链接文件
-p somefile :      测试文件是否存在并且是否为管道文件:
-S somefile :      测试文件是否存在并且是否为套接字文件:
-r somefile:       测试其有效用户(当前用户)是否对此文件有读取权限
-w somefile:       测试其有效用户是否对此文件有写权限
-x somefile:       测试其有效用户是否对此文件有执行权限
-s somefile:       测试文件是否存在并且不空

双目测试: file1 -nt file2 :测试file1的最近一次修改时间是否比file2更新一些 file1 -ot file2 :测试file1的最近一次修改时间是否比file2更老一些 在脚本中,把另外一个文件读取进来使用source file,并且变量也会被当前脚本使用,source也可以使用.代替

练习

计算100以内所有正整数之和

#!/bin/bash
declare -i sum=0
for i in {1..100};do
	let sum=$sum+$i
done
echo "The sum is : $sum."

分别计算100以内所有偶数之和和奇数之和

#!/bin/bash
declare -ioddsum=0,evensum=0
for i in seq 1 2 100;do
	oddsum=$[$oddsum+$i]
done
for j in seq 2 2 100;do
	evensum=$[$evensum+$i]
done
echo "the even sum is $evensum,the oldsum is $oldsum"

计算当前系统所有用户的ID之和

#!/bin/bash
declare -i uidsum=0
for i in `cut -d: -f3 /etc/passwd`;do
	uidsum=$[$uidsum+$i]
done
echo "the uidsum is $uidsum"	

计算/etc/rc.d/rc.sysinit、/etc/init.d/functions和/etc/issue三个文件的字符数之和

#!/bin/bash
declare -i bytecount=0
for i in /etc/rc.d/rc.sysinit /etc/init.d/functions /etc/issue;do
	let bytecount=$bytecount+`wc -c $i | cut -d'' -f1`
done
echo $bytecount

新建用户tmpuser1-tmpuser10,并计算他们的id之和

#!/bin/bash
declare -i uidsum=0
for i in seq 1 10;do
	useradd tmpuser$i
	let uidsum=$uidsum+`id -u tmpuser$i`
done
echo $uidsum

通过键盘给定一个已存在文件的路径,来判断文件内容的类型

#!/bin/bash
read -p "enter a file path:"filename
file $filename

通过键盘给定一个已存在目录的路径,来判断目录下文件内容的类型,没有输入的默认为根

显示/proc/meminfo文件中的以大小写s的开头的行

egrep ‘^(S|s)’ /proc/meminfo

取出默认shell为非bash的用户

grep -v 'bash$' /etc/passwd | cut -d: -f1

取出默认shell为bash的且其ID号最大的用户

grep 'bash$' /etc/passwd | sort -n -t: -k3 | tail -1|cut -d: -f1

显示/etc/rc.d/rc.sysinit文件中,以#开头,后面跟至少一个空白字符,而后又有至少一个非空白字符的行

grep '^#[[:space:]]\{1,\}[^[:space:]\{1,\}]' /etc/rc.d/rc.sysinit

显示/boot/grub/grub.conf中以至少一个空白字符开头的行

grep “^[[:space:]]\{1,\}[^[:space:]]\{1,\}” /boot/grub/grub.conf

查出/etc/passwd中一位数或两位数

grep "\<[0-9]\{1,2\}\>" /etc/passwd

找出ifconfig命令结果中的1到255之间的整数

ifconfig | egrep '\<([1-9]|[0-9])|1[0-9][0-9]|2[0-4][0-9]|25[0-5]/>'

查看当前系统上root用户的所有信息

grep '^root\>' /etc/passwd

添加用户bash和testbash,而后找出当前系统上与用户名和默认shell相同的用户

grep ‘^\([[:alnum:]]\{1,\}\)\>.*\1$’ /etc/passwd

找出netstat -tan命令执行的结果中以“listen”或establish结尾的行

netstat-tan | egrep '(linten|establish)[[:space:]]*$'

取出当前系统上所有用户的shell,要求:每种shell中显示一次,且升序排序显示

cut -d: -f7 /etc/passwd| sort -u

写一个脚本,分别统计/etc/rc.d/rc.sysinit、/etc/init.d/functions和/etc/fstab文件中各自以#开头的行的行数,以及空白行的行数

写一个脚本,分别复制/etc/rc.d/rc.sysinit、/etc/init.d/functions和/etc/fstab文件至/tmp目录中,文件名为原名后跟上当前日期组成

写一个脚本,显示当前系统上所有默认shell为bash的用户的用户名、UID以及其在/etc/passwd文件中的行号

写一个脚本,让用户输入一个文件,判断文件的空白行,并输出给用户,如果没有空白行则提示用户没有空白行

#!/bin/bash
read -p "enter a file path:" filename
if grep '^$' $filename &>/dev/null;then
	num=`grep '^$' $filename | wc -l`
	echo "$filename has $num blanklines"
else
	echo "$filename have no blankline"
fi

写一个脚本

比较两个数值大小,数值使用脚本参数传递

#!/bin/bash
if [ $# -lt 2 ];then
	echo "Stupid..."
	echo "`basename $0` argu1 argu2"
	exit 4
fi
if [ $1 -gt $2 ];then   //条件也可以写为test $1 -gt $2
	echo " The max num is $1"
else
	echo " The max num is $2"
fi

写一脚本,实现如下功能: 1、让用户通过键盘输入一个用户名 2、如果用户存在,就显示其用户名和UID; 3、否则,就显示用户不存在;

#!/bin/bash
read -t 10 -p"enter a username:" username    //username=${username:-root} use default setting
if id $username &> /dev/null;then      //grep "^root/>" /etc/passwd
	echo "$username:`id -u $username`"
else
	echo "$username not exist"
fi

写一脚本,实现如下功能: 1、让用户通过键盘输入一个用户名,如果用户不存在就退出; 2、如果用户的UID大于等于500,就说明它是普通用户; 3、否则,就说明这是管理员或系统用户;

#!/bin/bash
# exit 6 --- 
read -t 10 -p"enter a username:" username 
if ! id $username &> /dev/null;then
	echo "$username not exists"
	exit 6
fi

if [ `id -u $username` -ge 500 ];then
	echo "A common user"
else
	echo "Admin or system user"
fi

写一脚本,实现如下功能:

1、让用户通过键盘输入一个用户名,如果用户不存在就退出; 2、如果其UID等于其GID,就说它是个"good guy" 3、否则,就说它是个“bad guy”;

判断当前系统的所有用户是goodguy 还是bad guy

for username in `cut -d: -f1 /etc/passwd ` ;do

#! /bin/bash
read -t 10 -p"enter a username:" username 
if ! id $username &> /dev/null;then
	echo "$username not exists"
	exit 6
fi
if [ `id -u $username` -eq `id -g $username` ];then
	echo "good guy"
else
	echo "bad guy"
fi

写一个脚本,实现如下功能:

1、添加10个用户stu1-stu10;但要先判断用户是否存在; 2、如果存在,就用红色显示其已经存大在 3、否则,就添加此用户;并绿色显示; 4、最后显示一共添加了几个用户;

#! /bin/bash
declare -i usercount=0
for i in {1..10};do
	if id stu$i &> /dev/null;then
		echo -e "\033[31mstu&i\033[0m exists"
	else
	useradd stu$i && echo -e "\033[32mstu$i\033[0m finished."
	let usercount++
	fi
done

echo "add $usercount users"	

写一个脚本,求200以内所有3的整数倍的正整数的和

#!/bin/bash
declare -i sum=0
for i in {1..200};do
	if [ $[$i%3] -eq 0 ];then
		let sum+=$i
	fi
done
echo "sum is $sum"

写一个脚本,让用户交互式输入一个用户名,先判断用户名是否存在;不存在,则以7为退出码;判断用户的shell是否为/bin/bash,如果是则显示bash user,退出码为0,否则显示not bash user 退出码为1

#!/bin/bash
read -p "enter a username:" username
if id ! $username &> /dev/null;then
	echo "no such user"
	exit 7
fi
usershell = `grep "$username\b" /etc/passwd | cut -d: -f7`
if [[ "$usershell"=="/bin/bash" ]];then
	echo "bash user"
	returnvalue=0
else
	echo "not bash user"
	returnvalue=1
fi
exit $returnvalue

写一个脚本,显示如下菜单

cpu)show cpu info

men)show memory info

quit)quit

enter your option:

如果用户选择cpu,则显示文件/proc/cpuinfo的信息

如果用户选择mem,则显示文件/proc/meminfo的信息

如果用户选择quit则退出,且退出码为5

如果用户键入其他字符,则显示位置选项,请重新执行脚本;退出码为6

#!/bin/bash
returnvalue=0
read -p "enter your option:" useroption
useroption=`echo $useroption | tr 'A-Z' 'a-z'`
if [[ $useroption == "cpu" ]];then
	cat /proc/cpuinfo
elif [[ $useroption == "men" ]];then
	cat /proc/memoryinfo
elif [[ $useroption == "quit" ]];then
	echo "quit"
	returnvalue=6
else
	echo "unkown option"
	returnvalue=7
fi
exit $returnvalue

写一个脚本,分别复制/var/log下的文件至/tmp/logs目录中,复制目录时,使用r选项,复制文件时使用cp,复制链接文件时使用d选项,其他文件使用a选项

#!/bin/bash
targetdir='/tmp/logs'
[ -e $targetdir ]||mkdir $targetdir

for filename in /var/logs/*;do
	if [ -d $filename ];then
		copyCommand='cp -r'
	elif [ -f $filename ];then
		copyCommand='cp'
	elif [ -h $filename ];then
		copyCommand='cp -d'
	else
		copyCommand='cp -a'
	fi
	$copyCommand $filename $targetdir
done