《Linux命令行与shell脚本编程大全》(第三版)读书笔记

第一部分 Linux命令行

第三章、基本的bash shell命令

bash手册

man 命令

例子:

man cat
空格翻页、回车下一行、左右键看右侧(左侧)内容、q退出
info
info info
展示info页面,没看出有什么特别的
hostname

设置hostname

hostname mycomputer

浏览文件系统

遍历目录

cd

切换目录

cd /home/a  切换到/home/a目录,绝对路径
cd b    切换到当前目录下的b目录
cd .. 切换到上级目录
cd ../../ 切换到上级目录的上级目录
cd ~ 切换到用户家目录
cd 切换到用户家目录
cd - 切换到上次切换的目录
pwd

打印当前目录

文件和目录列表

ls

列出当前目录下的文件和目录

ls 输出当前目录下文件和目录
ls -F 同上,目录名后会有/方便区分
ls -a 同ls,同时展示隐藏文件
ls -R 递归显示子目录
ls -l 显示长列表,会有更多文件相关的信息
ls myscript 展示名字为myscript的文件或目录
ls myscri?t 过滤展示内容,问号代表一个任意字符
ls mysc* 过滤展示内容,星号代表多个任意字符
ls -l --time=atime 展示文件,时间列展示内容改为访问时间(默认是修改时间)

处理文件

创建文件

touch
touch a.log 创建a.log文件,如果该文件存在,更新该文件的修改时间
touch -a a.log 更新该文件的访问时间而非修改时间

复制文件

cp
cp test_1 test_2 把当前目录下的test_1复制到test_2
cp -i test_1 test_2 复制时如果有文件已经存在,会询问是否覆盖
cp ../test_3 . 复制上级目录的test_3到当前目录
cp ../test_3/*.log . 把上级目录中的test_3目录下的所有以.log结尾的文件复制到当前目录
cp -R test_1 test_2 拷贝时递归拷贝子文件夹

自动补全

在输入命令、目录名、文件名时制表符可以自动补全

链接文件

ln

创建链接

ln -s test.log s1_test.log 创建test.log的软链接(符号链接)s1_test.log
ln test.log h1_test.log 创建test.log的硬链接h1_test.log
readlink -f /usr/bin/python 如果/usr/bin/python是个链接文件,读取他的原始文件

重命名文件

mv

移动文件或重命名文件

mv a.log b.log 重命名a.log文件,改为b.log
mv ../a.log . 将上级目录中的a.log移动到当前目录

删除文件

rm

删除文件或目录

rm a.log 删除a.log
rm -i a.log 删除a.log,删除前询问是否删除
rm -f a.log 强制删除a.log,不询问
rm -r somedir 递归删除somedir中的内容

处理目录

创建目录

mkdir

创建目录

mkdir adir 创建adir目录
mkdir -p adir/bdir/cdir 递归创建目录(不存在的父级目录会自动创建)

删除目录

rmdir

删除目录,只有目录为空时才会删除,所以要先删除目录中的内容再删除目录

rmdir adir 删除adir目录

查看目录树

tree

打印目录树,默认可能没有安装

tree . 打印当前目录的树状结构

查看文件内容

查看文件类型

file

查看文件类型(文本、目录、链接、可执行文件等)

file a.log 查看a.log的文件类型

查看整个文件

cat

查看整个文件

cat a.log 查看a.log文件的内容
cat -n a.log 查看a.log文件内容,带行号
cat -b a.log 查看a.log文件内容,非空行带行号
cat -T a.log 查看a.log文件内容,不显示制表符(用^I代替)
more

逐页查看文件内容

more a.log 逐页查看a.log的文件内容,翻页方法和man相同
less

逐页查看文件内容,可以倒退

less a.log 逐页查看a.log的文件内容,翻页方法和more相同,但是在原有基础上增加了上下翻页的功能键(PgUp和PgDn)

查看部分文件

tail

查看文件末尾(默认10行)的内容

tail a.log 查看a.log末尾的内容
tail -n 20 a.log 查看a.log末尾20行的内容
tail -f a.log 动态查看a.log末尾的内容(即如果其他程序写入了a.log,这里会实时显示出来)

查看文件开始(默认10行)的内容

head a.log 查看a.log文件开头的内容
head -n 20 a.log 查看a.log文件开头20行的内容

第四章、更多的bash shell命令

监测程序

探查进程

ps

ps的基本输出显示了程序的进程id(PID)、运行在哪个终端(TTY)、以及进程已用的CPU时间

完成输出格式中,UID:启动进程的用户,PID:进程ID,PPID:父进程ID,C:进程生命周期中的CPU利用率,STIME:进程启动时的系统时间,TTY:进程启动时的终端设备,TIME:累积CPU时间,CMD:启动的程序名称

ps 显示运行在当前控制台下的属于当前用户的进程
ps -e 显示所有进程
ps -f 显示完整格式的输出
Ps -l 显示长列表(不常用)
ps --forest 显示进程层级
ps -ef --forest 前几个的组合使用

实时监测进程

top

实时监测进程,q退出,d修改轮询间隔,f选择显示哪些字段字段d,F选择按哪个字段排序

top 实时监测进程

结束进程

linux信号
信号 名称 描述
1 HUP 挂起
2 INT 中断
3 QUIT 结束运行
9 KILL 无条件终止
11 SEGV 段错误
15 TERM 尽可能终止
17 STOP 无条件停止运行,但不终止
18 TSTP 停止或暂停,但继续在后台运行
19 CONT 在STOP或TSTP之后恢复执行
kill

kill命令通过进程id给进程发信号。默认情况发送TERM信号

kill 123 尽可能杀死pid是123的进程
kill -9 123 无条件杀死pid是123的进程
killall

通过进程名而不是PID来结束进程,支持通配符

killall http* 杀死所有以http开头的进程

监测磁盘空间

挂载存储媒体

mount

给出两个简单例子

mount 输出当前系统上挂载的设备列表,每行展示:媒体设备文件名、虚拟目录挂载点、文件系统类型、已挂载媒体的访问状态
mount -t vfat /dev/sdb1 /media/disk  将vfat格式的u盘/dev/sdb1挂载到/dev/sdb1目录
umount
umount [directory|device] 这个命令可以通过设备文件或挂载点来指定要卸载的设备

使用df命令

df

显示设备上还有多少磁盘空间

df 显示每个有数据的已挂载文件系统使用情况
df -h 同上,但是按照用户易读的形式显示数值

使用du命令

du

du显示某个特定目录的磁盘使用情况

du 当前目录下所有文件、目录、子目录的磁盘使用情况

处理数据文件

排序数据

sort
sort a.log 排序输出a.log内容,默认按照字典顺序
sort -n a.log 排序输出a.log的内容,将他们识别成数字排序
sort -M a.log 如果a.log以Jan等形式的三字符月份名开头,可以按照月份排序
sort -t ':' -k 3 -n /etc/passwd 排序/etc/passwd文件,按冒号分段,然后按第三段排序,并且将其作为数字进行排序
du -sh * | sort -nr 这里用了管道,查看当前目录下所有子文件、子文件夹的大小,然后当做数字倒排序(-r为倒排序)

搜索数据

grep

查找文件内容

grep [options] pattern [file] 查找某个文件中匹配pattern模式的字符的行
grep three a.log 查找a.log文件中包含three的行
grep -v three a.log 查找a.log文件中不包含three的行
grep -n three a.log 查找a.log文件中包含three的行,并添加行号
grep -c three a.log 统计a.log文件中包含three的有多少行
grep -e three -e two a.log 查找a.log中包含three或two的行,-e可以用来指定多个匹配模块
grep [tf] a.log 查找a.log中包含t或包含f的行
egrep

grep的衍生,支持POSIX扩展正则表达式

fgrep

grep的衍生,支持将匹配模式指定为用换行符分割的一列固定长度的字符串

压缩数据

gzip

用来压缩文件

gzip a.log 压缩a.log文件
gzip my* 压缩my开头的所有文件(不压缩目录)
gzcat

用来查看压缩过的文本文件的内容

gunzip

用来解压文件

归档数据

tar
tar -cvf test.tar test/ test2/ 创建test.tar的归档文件,还有test和test2目录内容
tar -tf test.jar 列出test.jar的内容
tar -xvf test.jar 从test.tar中提取内容

第五章 理解shell

shell的父子关系

bash 从当前shell启动一个shell 
bash 从刚启动的shell再启动一个shell
ps --forest 展示进程树,可以看出父子关系

进程列表

echo $BASH_SUBSHELL
pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls ; echo $BASH_SUBSHELL 这是一个命令列表,最后一行显示0,表示没有生产子shell
(pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls ; echo $BASH_SUBSHELL) 这是一个进程列表,最后一行显示1,表示生产了一个子shell
(pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls ; (echo $BASH_SUBSHELL)) 这是一个进程列表,最后一行显示2,表示生产了两个子shell

别出心裁的子shell用法

sleep
sleep 10 休息10秒
sleep 10 & 在后台休息10秒,马上输入后台作业号和进程id
jobs
jobs 显示当前运行在后台模式中的所有用户的进程
jobs -l 显示当前运行在后台模式中的所有用户的进程以及它们的pid
coproc

协成可以同时做两件事:在后台生产子shell,并在这个子shell中执行命令

coproc sleep 10 在后台创建shell并执行休眠10秒
coproc My_Job { sleep 1000; } 在后台创建shell并执行休眠1000秒,为这个后台程序起名为My_Job,注意,格式要求严格,空格和分号需要严格按照格式

理解shell的內建命令

外部命令

外部命令也被称为文件系统命令,是存在于bash shell之外的程序。执行他们时回创建出一个子进程,这种操作被称为衍生(forking)。

ps -f 命令可以很清楚的看出,他创建了一个子进程,ps是一个外部命令
which

这个命令可以找出可执行命令的位置

which ps 可以找出ps命令的位置(一般情况下是在/bin/ps)

內建命令

內建命令和shell编译在一起,不需要子进程执行,效率更高。

type

內建命令,可以查看命令的类型

type cd 显示可执行命令cd的类型(会显示类似于shell builtin,说明是內建命令)
type -a echo 显示可执行命令echo的类型,如果有多个,全部列出
history

內建命令,bash shell会记录用过的命令,可以唤回并重新使用

history 显示最近用过的命令列表
!! 执行理事列表中最近的命令
history -a 强制将理事记录写入.bash_history文件,默认情况下退出时才会写入
!1082 执行命令历史记录编号为1082的命令
alias

內建命令,可以为长远命令及其参数创建别名,减少输入量。别名仅在它所被定义的shell进程中才有效

alias -p 查看当前可用别名
alias li='ls -li' 为ls -li 命令创建别名li,以后直接执行li即可

第六章 使用Linux环境变量

什么是环境变量

全局环境变量

全局环境变量会对shell会话和所有生成的子shell都是可见的。

printenv 或 env
printenv 打印全局环境变量
env 打印全局环境变量
printenv HOME 打印名为HOME的全局变量
echo $HOME 打印名为HOME的全局变量
cd $HOME 进入HOME所指的目录

局部环境变量

局部环境变量只能在定义他们的进程中可见。

set
set 输出局部变量、全局变量及用户定义变量(比env、printenv更详细)

设置用户定义变量

用户定义变量需要用小写,避免重新定义系统环境变量(系统环境变量都是大写)带来的灾难

my_var=Hello 定义my_var变量的值Hello;注意不能有空格
my_var2='hello world' 定义my_var2变量的值为hello world;注意不能有空格,值中若有空格,用单引号括起来

设置全局环境变量

在设定全局环境变量的进程所创建的子进程中,该变量都是可见的。

export

使用export将用户已经定义好的局部环境变量导出到全局环境中。

my_var=hello 创建局部环境变量my_var
export my_var 将my_var导出到全局环境中

删除环境变量

unset
unset my_var 删除名为my_var的环境变量

设置PATH环境变量

PATH=$PATH:/home/raul/Scripts 把PATH环境变量加上/home/raul/Scripts(PATH环境变量中用冒号分割)
PATH=$PATH:. 把PATH环境变量加上当前目录

定位系统环境变量

登录shell

/etc/profile 系统默认的主启动文件,每个用户登录时都会执行这个启动文件
$HOME/.bash_profile 用户专属启动文件
$HOME/.bashrc 用户专属启动文件
$HOME/bash_login 用户专属启动文件
$HOME/.profile 用户专属启动文件

交互式shell进程

如果bash shell不是登录系统时启动的(比如是在命令行提示符下敲bash时启动),那么启动的这个shell叫做交互式shell。它不会访问/etc/profile,只会检查$HOME/.bashrc文件

非交互式shell

通过BASH_ENV环境变量来配置需要执行的启动文件

环境变量持久化

全局性的环境变量可以在/etc/profile.d目录中创建一个以.sh结尾的文件,将全局环境变量设置放入其中
个人环境变量放到$HOME/.bashrc中
非交互式shell的设置文件配置到BASH_ENV环境中

数组变量

环境变量可以作为数组使用

mytest=(one two three four five) 把一个数组存入mytest环境变量中
echo $mytest 只会显示第一个值one
echo ${mytest[2]} 显示数组的第三个值
echo ${mytest[*]} 显示数组的所有值
mytest[2]=seven 修改数组第三个值,改为seven
unset mytest[2] 去掉数组第三个值(数组长度并未改变,该位置存放了‘空’)
unset mytest 删除整个数组

第七章、理解Linux的安全性

Linux的安全性

用户账户存在/etc/passwd文件,展示:用户登录名、用户密码、用户账户的UID、用户账户的组ID(GID)、用户账户的文本描述、用户HOME目录的位置、用户的默认shell

用户密码存在/etc/shadow文件,暂时:用户登录名、加密后的密码、自上次修改密码后过去的天数(自1970.1.1开始计算)、多少天后才能更改密码、多少天后必须更改密码、密码过期前提前多少天提醒用户更改密码、密码过期后多少天禁用用户账户、用户账户被禁用的日期(自1970.1.1到当天的天数)、预留字段给将来使用

添加新用户

useradd
useradd -D 查看添加新用户的默认配置
useradd test 添加test用户
useradd -D -s /bin/tsch 修改添加新用户时shell的默认值为/bin/tsch

删除用户

userdel
userdel test 删除test用户
userdel -r test 删除test用户,并且删除他的home目录和邮件目录

修改用户

usermod

修改用户账户的字段

参数有
-l 修改用户账户的登录名
-L 锁定账户,使用户无法登录
-p 修改账户的密码
-U 解除锁定,使用户能够登录
passwd
passwd 修改自己的密码
passwd test 修改test账户的密码
chpasswd
chpasswd < users.txt 用users.txt中的内容来修改密码,该文件中的内容应该是userid:passwd的键值对
chsh
chsh -s /bin/csh test 修改test用户的默认shell为/bin/csh
chfn

修改用户在/etc/passwd文件中的备注字段中的存储信息

chfn test 修改test的备注信息,会提示输入
finger
finger test 查看test用户的信息
chage

用来帮助管理用户账户的有效期

使用Linux组

组存在/etc/group文件下,分别展示:组名、组密码、GUID、属于该组的用户列表

创建新租

groupadd
groupadd shared 创建名为shared的组
usermod -G shared test 将test用户添加到shared组中

修改组

groupmod
groupmod -n sharing shared 修改组shared的名字,改为sharing

理解文件权限

默认文件权限

umask

默认权限由umask设置,比较复杂

umask 展示创建文件时的默认权限
umask 026 设置创建文件时的默认权限为640(666-026),创建目录时的默认权限751(777-026)

改变安全性设置

改变权限

chmod

两种方式,8进制权限码或符号模式

chmod 760 newfile 设置newfile的权限为 -rwxrw----
chmod [ugoa...][+-=][rwxXstugo...]
其中第一组定义了权限作用的对象:u代表用户、g代表组、o代表其他、a代表上述所有
第二组定义了是想 在现有权限上增加权限(+),还是现有权限上移除权限(-),或是将权限设置成后面指定的值(=)
第三组代表作用到设置上的权限:
X:如果对象是目录或者它已有执行权限,赋予执行权限
s:运行时重新设置UID或GID
t:保留文件或目录
u:将权限设置为跟属主一样
g:将权限设置为跟属组一样
o:将权限设置为跟其他用户一样

chmod o+r newfile 给newfile的其他用户增加读权限
chmod u-x newfile 移除newfile的属主的执行权限

改变所属关系

chown
chown raul newfile 改变newfile的属主为raul
chown raul.shared newfile 改变newfile的属主为raul,属组为shared
chown .rich newfile 改变newfile的属组为rich
chown raul. newfile 将newfile的属主和属组都改为raul
chown -R raul newdir 递归的将newdir下的所有文件和目录的属主都改为raul
chgrp
chgrp shared newfile 修改newfile的属组为chgrp

共享文件

可以创建共享文件夹,如果有需要了解细节再仔细学习,下面给出方法

mkdir testdir 创建共享目录
chgrp shared testdir    将属组改为需要共享文件的组shared
chmod g+s testdir 修改权限
umask 002 所有用户的umask都需要改成002
cd testdir  进入目录
touch testfile  创建文件,这个文件的属组会是shared

第八章、管理文件系统

操作文件系统

创建分区

fdisk
fdisk /dev/sdb 给存储设备/dev/sdb分区
进入后,有如下有用的操作
p 显示这个存储设备的详细信息
n 创建新分区
w 保存更改

创建文件系统

分区数据使用前,应先用某种文件系统对其进行格式化,每个文件系统相对应的格式化工具不同。

mkefs   创建一个ext文件系统
mke2fs  创建一个ext2文件系统
mkfs.ext3   创建一个ext3文件系统
mkfs.ext4   创建一个ext4文件系统
mkreiserfs  创建一个ReiserFS文件系统
jfs_mkfs    创建一个JFS文件系统
mkfs.xfs    创建一个XFS文件系统
mkfs.zfs    创建一个ZFS文件系统
mkfs.btrfs  创建一个Btrfs文件系统

示例

sudo mkfs.ext4 /dev/sdb1 在/dev/sdb1分区上创建一个ext4文件系统

格式化后,需要将这个文件系统挂载到虚拟目录中才能使用

ls /mnt
sudo mkdir /mnt/my_partition
ls -al /mnt/my_partition/
ls -dF /mnt/my_partition
sudo mount -t ext4 /dev/sdb1 /mnt/my_partition
ls -al /mnt/my_partition

这种挂载方式是临时的,如果想linux启动时自动挂载,可以将其添加到/etc/fstab文件中

文件系统的检查与修复

fsck

fsck可以检查和修复大部分类型的Linux文件系统(包含ext、ext2、ext3、ext4、ReiserFS、JFS、XFS)。需要先卸载目录再检查,检查完后再挂载。

fsck options filesystem 对文件系统进行fsck命令
-a 如果检测遇到错误,自动修复
-A 检查/etc/fstab/文件中列出的所有文件系统
-C 如果支持进度条功能,显示进度条
-N 不进行检查,只显示哪些检查会执行
-r 出现错误时提示
-R 使用-A选项时跳过根文件系统
-s 检查多个文件系统时,依次进行检查
-t 指定要检查的文件系统类型
-T 启动时不显示头部信息
-V 在检查时产生详细输出
-y 检测到错误时自动修复文件系统

逻辑卷管理

使用Linux LVM

fsdisk中分区类型8e即表示这个粪污将会被用作Linux LVM系统的一部分,而不是一个直接的文件系统。

fsdisk 命令,输入t,然后输入8e

下一步是用分区创建实际的物理卷

sudo pvcreate /dev/sdb1 创建实际的物理卷
sudo pvdisplay /dev/sdb1 查看创建进度
sudo vgcreate Vol1 /dev/sdb1 创建卷组
sudo vgdisplay Vol1 查看刚创建的卷组的细节
sudo lvcreate -l 100%FREE -n lvtest Vol1 创建逻辑卷lvtest(参数细节查看lvcreate --help或man lvcreate)
sudo lvdisplay Vol1 查看刚创建的逻辑卷的详细情况

逻辑卷创建后,还需要创建文件系统,挂载到虚拟目录中

sudo mkfs.ext4 /dev/Vol1/lvtest 为逻辑卷创建ext4文件系统
sudo mount /dev/Vol1/lvtest /mnt/my_partition 把逻辑卷挂载到/mnt/my_partition目录下

其他命令

vgchange 激活和禁用卷组
vgremove 删除卷组
vgextend 将物理卷加到卷组中
vgreduce 从卷组中删除物理卷
lvextend 增加逻辑卷的大小
lvreduce 减小逻辑卷的大小

第九章、安装软件程序

基于Debian的系统

用aptitude管理软件包

aptitude工具本质上是apt工具和dpkg的前端,使用它有助于避免常见的软件安装问题,如软件依赖关系缺失、系统环境不稳定及其他一些不必要的麻烦。

aptitude
aptitude 进入aptitude的全屏模式
aptitude show mysql-client 显示mysql-client的详情(以及是否已经安装等信息)
dpkg
dpkg -L vim-common 列出vim-common软件包所安装的全部文件
dpkg --search /user/bin/xxd 查找某个特定文件属于哪个软件包

用aptitude安装软件包

aptitude search wine 查找所有名字中包含wine的软件包(如果行首显示i u则表示已经安装,显示p v说明这个包可用,但还没安装)
sudo aptitude install wine 安装名字包含wine的软件包

用aptitude更新软件

sudo aptitude safe-upgrade 将所有已安装的包更新到软件仓库中的最新版本,会检查包之间的依赖关系,有利于系统稳定。
aptitude full-upgrade 同safe-upgrade,但不会检查包与包之间的依赖关系。
aptitude dist-upgrade 同safe-upgrade,但不会检查包与包之间的依赖关系。

用aptitude卸载软件

sudo aptitude remove wine 只删除软件包而不删除数据和配置文件
sudo aptitude purge wine 同时删除软件包以及相关的数据和配置文件

aptitude仓库

/etc/apt/sources.list aptitude默认的软件仓库配置文件,如果需要自定义,修改这里(最好不要自定义,原始配置中的软件包版本不会互相冲突)

基于Red Hat的系统

列出已安装包

yum
yum list installed 查看系统上已经安装的包
yum list xterm 查看xterm软件包的详细信息
yum list installed xterm 查看xterm软件包是否已经安装
yum provides /etc/yum.conf 查看/etc/yum.conf文件是属于哪个软件包

用yum安装软件

yum install xterm 安装xterm软件包
yum localinstall package_name.rpm 手动安装下载好的package_name.rpm文件
su
su hadoop 切换hadoop用户
su - hadoop 切换hadoop用户,并进入hadoop的家目录
su - 切换到root用户,并进入root的家目录

用yum更新软件

yum list updates 列出所有已安装包的可用更新
yum update package_name 更新指定的软件包
yum update 更新所有可更新的包

用yum卸载软件

yum remove package_name 只删除软件包而保留配置文件和数据
yum erase package_name 同时删除软件包和相关的配置和数据

处理损坏的包依赖关系

有时在安装多个软件包时,某个软件包的依赖关系可能会被另一个包的安装覆盖,这叫做损坏的包依赖关系。解决办法如下

首先尝试

yum clean all
yum update

如果不行

yum deplist package_name 可以查看这个软件包所需要的库由那些软件提供,然后可以安装他们。

如果还是不行

yum update --skip-broken 更新所有软件包,忽略依赖关系损坏的那个包。这个虽然不能解决问题,但至少可以减少影响。

yum软件仓库

软件仓库的配置文件在/etc/yum.repos.d目录下

yum repolist 查看现有软件仓库

从源码安装

首先下载源码包

tar -zxvf name.tar.gz 解压缩软件包
cd name 进入刚刚解压的软件包目录,查看README文件或AAAREADME文件。该文件中包含了软件安装所需要的操作

下面三条为常用的从源码安装的步骤

./configure 这个命令检查Linux系统,确保有合适的编译器可以编译源代码,另外确保有正确的库依赖关系
make 编译源码,创建可执行文件
make install 安装编译好的可执行文件到系统中

第十章、使用编辑器

vim编辑器

vim基础

vim myprog.c 打开文件,如果文件不存在,则打开临时文件

进入vim后有两种模式:普通模式和插入模式。刚打开时vim编辑器会进入普通模式。普通模式中,vim编辑器会将案件解释成命令。在插入模式下,vim会将在当前光标位置的输入插入到缓冲区。

i 进入插入模式,若要退出插入模式回到普通模式,按下退出键(ESC)
h 左移一个字符
j 下移一行
k 上移一行
l 右移一个字符
PageDown(或Ctrl+F) 下翻一屏
PageUp(或Ctrl+B)上翻一屏
G 移动到缓冲区的最后一行
num G 移动到缓冲区中的第num行
gg 移动到缓冲区的第一行

在普通模式下输入冒号,会进入普通模式下的一个特别功能:命令行模式。在命令行模式中有如下命令:

q 如果未修改缓冲区数据,退出
q! 取消所有对缓冲区的修改并退出
w filename 将文件保存到另一个文件中
wq 将缓冲区数据保存到文件中并退出

编辑数据

在普通模式下,vim编辑器提供了一些命令来编辑缓冲区中的数据。

x 删除当前光标所在位置的字符
2x 删除从当前光标所在位置开始的2个字符
dd 删除当前光标所在行
5dd 删除从当前光标所在行开始的5行数据
dw 删除当前光标所在位置的单词
d$ 删除当前光标所在位置至行尾的内容
J 删除当前光标所在行行尾的换行符(拼接行)
u 撤销前一编辑命令
a 在当前光标后追加数据
A 在当前光标所在行行尾追加数据
r char 用char替换当前光标所在位置的单个字符
R text 用text覆盖当前光标所在位置的数据,直到按下esc键

复制和粘贴

删除命令默认都会把删除内容放到一个单独的寄存器中,类似于剪切。
p 取出寄存器中的数据并追加到当前光标位置
yw复制一个单词
y$复制到行尾
v 移动光标到要开始复制的位置,按下v,光标所在位置已经高亮,这时可以移动光标覆盖想要复制的区域,最后按y来激活复制命令。

查找和替换

/ 在普通模式下按斜线,就是查找命令,输入查找的命令后按回车。如果没有找到,会报错,如果找到了,会显示光标后第一个(如果光标后没有,则从缓冲区第一行开始搜索),如果要查看下一个,键入n或者键入/后键入回车。
:s/old/new/ 命令行模式中使用,vim编辑器会调到old第一次出现的地方,并用new替换
:s/old/new/g 将当前行的所有old替换成new
:n,ms/old/new/g 替换行号n和m之间的所有old
:%s/old/new/g 替换整个文件中的所有old
:%s/old/new/gc 替换整个文件中的所有old,但在每次出现时提示

nano编辑器

nano编辑器窗口的地步显示了各种命令及简要描述。脱字符(^)表示Ctrl键,组合键大小写不敏感。

CTRL+C 显示光标在文本编辑缓冲区中的位置
CTRL+G 显示nano的主帮助窗口
CTRL+J 调整当前文本段落
CTRL+K 剪切文本行,并将其保存在剪切缓冲区
CTRL+O 将当前文本编辑缓冲区的内容写入文件
CTRL+R 将文件读入当前文本编辑缓冲区
CTRL+T 启动可用的拼写检查器
CTRL+U 将剪切缓冲区中的内容放入当前行
CTRL+V 翻动到文本编辑缓冲区中的下一页内容
CTRL+W 在文本编辑缓冲区中所搜单词或短语
CTRL+X 关闭当前文本编辑缓冲区,退出nano,返回shell
CTRL+Y 翻动到文本编辑缓冲区中的上一页内容

emacs编辑器

emacs编辑器使用包括控制键(PC键盘上的Ctrl键)和Meta键的按键组合。Meta一般被映射到Alt键。emacs官方文档将Ctrl键缩写为C-,而Meta键缩写为M-。

在控制台中使用emacs

C-p 上移一行
C-b 左移一字符
C-f 右移一字符
C-n 下移一行
M-f 右移到下一个单词
M-b 左移到上一个单词
C-a 移至行首
C-e 移至行尾
M-a 移至当前句首
M-e 移至当前句尾
M-v 上翻一屏
C-v 下翻一屏
M-< 移至文本的首行
M-> 移至文本的尾行
C-x C-s 保存当前缓冲区到文件
C-z 退出emacs并保持在这个会话中继续运行,以便切回
C-x C-c 退出emacs并停止该程序
M-Backspace 剪切光标当前所在位置之前的单词
M-d 剪切光标当前所在位置之后的单词
C-k 剪切光标当前所在位置至行尾的文本
M-k 剪切光标当前所在位置至句尾的文本
C-@或C-Spacebar 移动到待剪切区域的起始位置键入C-@或C-Spacebar,然后移动到结束位置按下C-w命令,两个位置间的内容将被剪切
C-/ 撤销剪切命令
C-y 粘贴
C-s 向下查找,可以直接输入文字(默认是渐进式查找,每输入一个字符都会从新触发查找并高亮结果),也可以输入回车,在查找前输入完整的待查找文本
C-r 向上查找,可以直接输入文字(默认是渐进式查找,每输入一个字符都会从新触发查找并高亮结果),也可以输入回车,在查找前输入完整的待查找文本
M-x replace-string 替换
C-x C-f 将新的文件加载到缓冲区
C-x C-b 列出工作缓冲区
C-x o 切换到缓冲区列表窗口
C-x b 输入你要切换到的缓冲区的名字
C-x 2 将窗口水平拆分成两个窗口
C-x 3 将窗口竖向拆分成两个窗口
C-x o 移动到另一个窗口
C-x 0 关闭当前窗口
C-x 1 关闭除当前窗口外的其他所有窗口

第二部分 shell脚本编程基础

构建基本脚本

使用多个命令

date ; who 先显示系统时间,再显示当前登录系统的用户
date

显示系统时间

who

显示当前登录系统的用户

创建shell脚本

创建shell脚本文件时,必须在文件的第一行指定要使用的shell,其格式为

#!/bin/bash 在通常的shell脚本中井号用作注释行,但是第一行例外,它告诉shell用哪个shell来运行脚本

在shell中,可以在文件的每一行输入命令,也可以使用分号分隔命令,例如:

#!/bin/bash
# This script displays the date and who's logged on
date
who

脚本建立后,需要赋予用户执行的权限才可以执行

显示消息

echo
echo This is a test 在控制台显示器上显示“This is a test”,并换行
echo -n "this is a test" 在控制台显示器上显示“This is a test”,但不换行

使用变量

环境变量

在脚本中,可以在环境变量名称前加上美元符来使用这些环境变量

echo HOME: $HOME 输出用户的$HOME环境变量
echo "HOME: $HOME" 同上
echo "HOME: ${HOME}" 同上,${HOME}是另一种写法
echo "HOME: \$HOME" 反斜杠使$表示他本身,这句的输出结果就是"HOME: HOME",而没有输出环境变量$HOME的值

用户变量

给变量赋值时,等号左右不能有空格。使用时也是用$符号引用

#!/bin/bash
days=10
guest="Eatie"
echo "$guest checked in $days days ago"

命令替换

shell脚本中最有用的特性之一就是可以从命令行输出中提取信息,并将其赋给变量。有两种方法可以将命令输出赋给变量:反引号字符(`)或$()格式

以下是一个例子,将date命令的输出赋予testing变量,然后再echo出来

#!/bin/bash
testing=$(date)
echo "The date and time are: " $testing

同样的,也可以使用反引号来做命令替换

#!/bin/bash
testing=`date`
echo "The date and time are " $testing

重定向输入和输出

输出重定向

date > date.log 将date命令的结果输出到date.log文件中,如果date.log文件已存在,则覆盖它
date >> date.log 将date命令的结果输出到date.log文件中,如果date.log文件已存在,则追加写到它的末尾

输入重定向

wc

wc命令可以对数据中的文本进行计数,默认情况下,输出3个值:行数、词数、字节数

wc < date.log 将文件内容重定向到wc命令中,统计行数、词数等信息

还有一种输入重定向的方法,称为内联输入重定向。这种方法无需使用文件进行重定向,只需要在命令行中输入用于重定向的数据就可以了。

wc << eof 使用用户输入的数据作为wc的输入,直到用户输入“eof”这个词的时候才结束输入并执行wc的命令

管道

使用|符号,将前一个命令的输出作为后一个命令的输入。管道两边都是命令,而重定向的两边有一边是文件。

ls -l | more 执行ls -l命令列出文件列表,并将输出当做more的输入,使我们可以更方便的查看输出结果

执行数学运算

expr命令

这个命令笔记笨拙,一般不使用

expr
expr 1 + 5 计算1+5的值,结果为6,空格要严格
expr 1 \* 5 计算1乘以5,\*用来代表乘以,因为*在shell中有其他的意义

使用方括号

这个是更简单的执行数学表达式的方法:使用美元符和方括号将数学表达式围起来

echo $[1*5] 输出1乘以5

使用在脚本中的例子:

#!/bin/bash
var1=100
var2=50
var3=45
var4=$[$var1 * ($var2 - $var3)]
echo The final result is $var4

浮点解决方案

bash shell数学运算符只支持整数运算。要克服这个限制,有几种解决方案。

最常见的方案是使用內建的bash计算器,叫做bc

bc 进入bc计算器,在其中可以执行数学计算。
scale=4 进入bc后输入scale=4,可以让除法的运算结果保留4位小数

两个在脚本中使用bc的例子,第一个使用管道:

#!/bin/bash
var1=20
var2=3.14159
var3=$(echo "scale=4; $var1 * $var1" | bc)
var4=$(echo "scale=4; $var3 * $var2" | bc)
echo The final result is $var4

下面是使用内联输入重定向的方法:

#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc << EOF
scale = 4
a1 = ($var1 * $var2)
b1 = ($var3 * $var4)
a1 + b1
EOF
)
echo The final answer for this mess is $var5

退出脚本

查看退出状态码

Linux提供了一个专门的变量$?来报错上个已执行命令的退出状态码

echo $? 查看上一个命令的退出状态码,0是成结束。

退出状态码参考

状态码 描述
0 命令成功结束
1 一般性未知错误,如提供了无效参数
2 不适合的shell命令
126 命令不可执行,如没有权限
127 没找到命令
128 无效的退出参数
128+x 与Linux信号x相关的严重错误
130 通过Ctrl+c终止的命令
255 正常范围之外的退出状态码

exit命令

exit 5 退出命令,指定退出状态码为5(正常的脚本退出时退出状态码是0)

第十二章、使用结构化命令

使用if-then语句

格式如下,会执行if后的command命令,如果command命令的退出码为0,则执行then部分的命令,如果不为0则跳过,fi为if语句的结束

if command
then
    commands
fi

或者将if和then放在同一行

if command; then
    commands
fi

举一个例子,then部分可以使用不止一条命令

#!/bin/bash
if pwd
then
    echo It worked
    echo It worked2
fi

if-then-else语句

格式如下,会执行if后的command命令,如果command命令的退出码为0,则执行then部分的命令,如果不为0则执行else部分的命令,fi为if语句的结束

if command
then
    commands
else
    commands
fi

嵌套if

if语句可以嵌套

另外可以使用elif语句

if command1
then
    commands1
elif command2
then
    commands2
fi

可以在elif后接一个else

if command1
then
    commands1
elif command2
then
    commands2
esle
    commands3
fi

也可以使用多个elif

if command1
then
    commands1
elif command2
then
    commands2
elif command3
then
    commands3
esle
    commands4
fi

test命令

如果test命令中列出的条件成立,test命令就会退出并返回状态码0,如果不成立,就会退出并返回非零的退出状态码,格式非常简单

test condition

包含在if语句中的写法,如果不写condition部分,test会以非零的退出码退出

if test condition
then
    commands
fi

bash shell提供了另一种条件测试方法,无需在if-then语句中生命test命令,格式如下,空格严格

if [ condition ]
then
    commands
fi

数值比较

数值比较功能的参数

比较 描述
n1 -eq n2 检查n1是否与n2相等
n1 -ge n2 检查n1是否大于或等于n2
n1 -gt n2 检查n1是否大于n2
n1 -le n2 检查n1是否小于或等于n2
n1 -lt n2 检查n1是否小于n2
n1 -ne n2 检查n1是否不等于n2

字符串比较

字符串比较功能的参数

比较 描述
str1 = str2 检查str1是否和str2相同
str2 != str2 检查str1是否和str2不同
str1 < str2 检查str1是否比str2小
str1 > str2 检查str1是否比str2大
-n str1 检查str1的长度是否非0
-z str1 检查str1的长度是否为0

在使用<和>比较字符串大小时,必须对<和>进行转义,否则会被shell当做重定向符号

#!/bin/bash
val1=baseball
val2=hockey
if [ $val1 \> $val2 ]
then
    echo "$val1 is greater than $val2"
else
    echo "$val1 is less than $val2"
fi

文件比较

文件比较功能的参数

比较 描述
-d file 检查file是否存在并是一个目录
-e file 检查file是否存在
-f file 检查file是否存在并是一个文件
-r file 检查file是否存在并可读
-s file 检查file是否存在并非空
-w file 检查file是否存在并可写
-x file 检查file是否存在并可执行
-O file 检查file是否存在并属于当前用户所有
-G file 检查file是否存在并且默认组与当前用户相同
file1 -nt file2 检查file1是否比file2新
file1 -ot file2 检查file1是否比file2旧

符合条件测试

if-then语句允许使用布尔逻辑来组合测试。主要有两种布尔运算符可用:

[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]

if-then的高级特性

使用双括号

双括号允许你在比较过程中使用高级数学表达式,格式如下

(( expression ))

一些双括号命令符号列举

符号 描述
val++ 后增
val– 后减
++val 先增
–val 先减
! 逻辑求反
~ 位求反
** 幂运算
<< 左位移
>
右位移
& 位布尔和
| 位布尔或
&& 逻辑和
|| 逻辑或

可以在if语句中用双括号命令,也可以在脚本中的普通命令里使用来赋值,举例来说

#!/bin/bash
val1=10
if (( $val1 ** 2 > 90 ))
then
    (( val2 = $val1 ** 2 ))
    echo "The square of $val1 is $val2"
fi

使用双方括号

双方括号命令提供了针对字符串比较的高级特性,格式如下:

[[ expression ]]

他提供了test命令未提供的另一个特性:模式匹配。

使用双等号(==)将右边的字符串视为一个模式,举例如下

#!/bin/bash
if [[ $USER == r* ]]
then
    echo "Hello $USER"
else
    echo "Sorry, I do not know you"
fi

case命令

使用case命令就不需要再写出很多elif语句来不同检查同一个变量的值了。格式如下

case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) defaultCommands;;
esac

举例如下

#!/bin/bash
case $USER in
rich | barbara)
    echo "welcome, $USER"
    echo "Please enjoy your visit";;
testing)
    echo "Special testing account";;
jessica)
    echo "Do not forget to log off when you're done";;
*)
    echo "Sorry, you are not allowed here";;
esac

第十三章、更多的结构化命令

for命令

基本格式如下:

for var in list
do
    commands
done

或者:

for var in list; do
    commands
done

读取列表中的值

最后一次迭代的变量会在遍历结束后一直有效,除非修改它的值:

#!/bin/bash
for test in Alabama Alaska Arizona
do
    echo "The next state is $test"
done
echo "The last stage we visited was $test"

读取列表中的复杂值

如果列表中有单引号,则需要转义或用双引号扩起来;如果有值同时包含多个词,需要用双引号将多个词括起来

从变量读取列表

下面第三行代码可以向已有值的列表中拼接一个值

#!/bin/bash
list="Alabama Alaska Arizona"
list=$list" Connecticut"
for state in $list
do
    echo "Have you ever visited $state?"
done

从命令读取值

#!/bin/bash
file="states"
for state in $(cat $file)
do
    echo "Visit beautiful $state"
done

更改字段分隔符

默认情况下,bash shell的IFS(内部字段分隔符)包括空格、制表符、换行符

修改IFS,让它只识别换行

IFS=$'\n'

如果需要多个值,串起来就行

IFS=$'\n':;"

用通配符读取目录

#!/bin/bash
for file in /home/rich/test/*
do
    commands
done

C语言风格的for命令

C语言风格的for命令

c语言风格的for命令不需要遵守bash shell标准的for命令:变量赋值可以用空格;条件中的变量不以美元符开头;迭代过程的算式未用expr命令格式。

命令格式:

for (( variable assignment ; condition ; iteration process ))

示例:

#!/bin/bash
for (( i=1; i <= 10; i++ ))
do
    echo "The next number is $i"
done

使用多个变量

示例:

#!/bin/bash
for (( a=1, b=10; a <= 10; a++, b--))
do
    echo "$a - $b"
done

while命令

while命令的基本格式

只有测试命令的退出状态码不为0,才执行循环中的命令,否则退出循环。

格式:

while test commands
do
    other commands
done

例子:

#!/bin/bash
var1=10
while [ $var1 -gt 0 ]
do
    echo $var1
    var1=$[ $var1 - 1 ]
done

使用多个测试命令

例子:

#!/bin/bash
var1=10
while echo $var1
        [ $var1 -ge 0 ]
do
    echo "This is inside the loop"
    var1=$[ $var1 - 1 ]
done

until命令

until命令和while命令方式相反,只有测试命令的退出状态码不为0,才执行循环中的命令。

基本格式:

until test commands
do
    other commands
done

循环处理文件数据

示例为查看/etc/passwd文件每一个词的例子,其中使用了嵌套循环,和修改IFS环境变量

#!/bin/bash
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
    echo "Values in $entry -"
    IFS=:
    for value in $entry
    do
        echo "  $value"
    done
done
IFS=$IFS.OLD

控制循环

有两个命令:break、continue

break命令

可以用break命令退出任意类型的循环

跳出单个循环

示例:

#!/bin/bash
for var1 in 1 2 3 4 5 6 7 8
do
    if [ $var1 -eq 5 ]
    then
        break
    fi
    echo "Iteration number: $var1"
done
echo "The for loop is completed"
跳出多个循环

break接受一个参数,指定从内至外跳出几层循环,默认为1。如果跳出多个循环时,可以使用这个参数,格式为:

break n

continue命令

continue命令提前终止某次循环中的命令,但不会完全终止整个循环。

continue用法格式和break类似,也可以接收参数n。

处理循环的输出

可以对循环的输出使用管道或进行重定向

重定向示例:

#!/bin/bash
for file in /home/rich/*
do
    if [ -d "$file" ]
    then
        echo "$file is a directory"
    elif
        echo "$file is a file"
    fi
done > output.txt

管道示例:

...
done | sort

实例

创建多个用户账户

这个例子使用了read命令,read命令可以自动读取.csv文件的下一行,没有下一行时返回FALSE

示例:

#!/bin/bash
input="users.csv"
while IFS=',' read -r userid name
do
    echo "adding $userid"
    useradd -c "$name" -m $userid
done < "$input"

第十四章、处理用户输入

命令行参数

命令行参数以空格分隔,如果参数本身带有空格,则需要用单引号或双引号括起来

读取参数

bash shell会将一些称为位置参数的特殊变量分配给输入到命令行中的所有参数。位置参数变量是标准的数字: 0 1是第一个参数, 2 9

读取脚本名

读取脚本名时,如果使用全路径,路径也会在$0变量中,可以使用basename命令返回不包含路径的脚本名,示例如下:

#!/bin/bash
name=$(basename $0)
echo 
echo The scripte name is: $name

测试参数

测试参数是否存在数据,示例:

#!/bin/bash
if [ -n "$1" ]
then
    echo Hello $1
else
    echo "Sorry, you did not identify yourself."
fi

特殊参数变量

参数统计

$# 特殊变量,含有脚本运行时携带的命令行参数的个数
${!#} 特殊变量,表示参数列表的最后一个参数

抓取所有数据

@都是存储所有变量,但是前者把所有变量存放成单个参数,而后者可以遍历

移动遍历

shift命令能够用来操作命令行参数,会根据他们的相对位置来移动命令行参数。默认情况下它会将每个参数变量向左移动一个位置。 3 2, 2 1, 1 0存的是程序名,不会改变。在不知道参数有多少,又要遍历参数列表时很方便,示例如下:

#!/bin/bash
count=1
while [ -n "$1" ]
do
    echo "Parameter #$count = $1"
    count=$[ $count + 1 ]
    shift
done

另外也可以一次性移动多个位置,只需要提供一个参数,格式:

shift n

处理选项

查找选项

可以简单的在shell脚本中做判断,不推荐

使用getopt命令

getopt可以接受一系列任意形式的命令行选项和参数,并将他们转换成适当的格式,。命令格式如下:

getopt optstring parameters 

其中,optstring定义了格式(在optstring中列出要在脚本中用到的每个命令行选项字母,在每个需要参数值的选项字母后加一个冒号),parameters是需要解析的参数列表。示例如下:

getopt ab:cd -a -b test1 -cd test2 test3

上面例子定义了4个有效选项字母:a、b、c、d。冒号放在b后面,因为b需要一个参数值。

如果要去掉错误信息,可以在optstring前面加-q,举例如下:

getopt -q ab:cd -a -b test1 -cde test2 test3

在脚本中使用时,需要使用set命令将命令行参数替换成set命令的命令行值使用举例:

#!/bin/bash
set -- $(getopt -q ab:cd "$@")
while [ -n "$1" ]
do
    case "$1" in
    -a) echo "Found the -a option" ;;
    -b) param="$2"
        echo "Fountd the -b option, with parameter value $param"
        shift ;;
    -c) echo "Found the -c option" ;;
    --) shift
        break ;;
    *) echo "$1 is not an option" ;;
    esac
    shift
done
#
count=1
for param in "$@"
do
    echo "Parameter #$count: $param"
    count=$[ $count + 1 ]
done

getopt无法识别带空格和引号的参数值

使用更高级的getopts

getopts一次只处理命令行上检测到的一个参数,处理完所有参数后,会退出并返回一个大于0的退出状态码,非常适合用于解析所有参数的循环中

命令格式如下:

getopts optstring variable

optstring类似getopt的那个,有效选项字母都在optstring中,如果选项有参数,在后面加个冒号。如果要去掉错误消息,可以在optstring之前加一个冒号。当前参数保存在命令行中定义的variable中

getopts命令会用到两个环节变量,如果选项需要跟一个参数值,OPTARG环境变量会保存这个值,OPTIND环境变量保存了参数列表中getopts正在处理的参数位置,以便在处理完选项之后继续处理其他命令行参数。

使用示例如下:

#!/bin/bash
echo
while getopts :ab:c opt
do
    case "$opt" in
        a) echo "Found -a option" ;;
        b) echo "Found -b option, with value $OPTART" ;;
        c) echo "Found -c option" ;;
        *) echo "Unknown option: $opt";;
    esac
done
#
shift $[ $OPTIND -1 ]
#
echo
count=1
for param in "$@"
do
    echo "Parameter $count: $param"
    count=$[ $count + 1 ]
done

上面例子中,使用$OPTIND和shift将前面选项部分的参数移除,只剩下后面的参数部分

getopts的优势,可以在参数中使用空格,可以将字母和参数值放在一起使用,例如:

./test19.sh -abtest1

将选项标准化

有些选项有约定俗成的用法

选项 描述
-a 显示所有对象
-c 生成一个计数
-d 指定目录
-e 扩展一个对象
-f 指定读入数据的文件
-h 显示命令的帮助信息
-i 忽略大小写
-l 产生输出的长格式版本
-n 使用非交互模式(批处理)
-o 将所有输出重定向到指定的输出文件
-q 安静模式运行
-r 递归处理
-s 安静模式运行
-v 生成详细输出
-x 排除某个对象
-y 对所有问题回答yes

获得用户输入

获得用户输入可以使用read命令

基本的读取

read命令从标准输入或另一个文件描述符中接受输入,在收到输入后,会将数据放入一个变量

示例:

#!/bin/bash
echo -n "Enter your name: "
read name
echo "Hello $name, welcome to my program."

read可以使用-p选项,允许指定提示符,示例:

read -p "Please enter your age: " age

read可以指定多个变量,示例:

read -p "Enter your name: " first last

read可以不指定变量,这样会将输入存放到特殊环境变量REPLY中,示例:

#!/bin/bash
read -p "Enter your name: "
echo
echo Hello $REPLY, welcome!

超时

可以使用-t来指定一个计时器

#!/bin/bash
if read -t 5 -p "Please enter your name: " name
then
    ...
else
    ...
fi

也可以使用-n来指定接受几个字符,例如-n1,read命令会在接受到一个字符后自动退出,示例:

#!/bin/bash
read -n1 -p "Do you want to continue [Y/N]? " answer
case $answer in
Y | y)  echo
        echo "fine,continue on...";;
N | n)  echo
        echo OK, goodbye
        exit;;
esca

隐藏方式读取

-s选项可以避免read命令中输入的数据出现在显示器上,示例如下:

read -s -p "Enter your password: " pass

从文件中读取

示例如下:

#!/bin/bash
count=1
cat test | while read line
do
    ...
    count=$[ $count + 1 ]
done

第十五章、呈现数据

理解输入和输出

标准文件描述符

每个进程一次最多可以有9个文件描述符,bash shell保留了前三个文件描述符(0、1和2),见下表

文件描述符 缩写 描述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误

使用重定向符号(<)时,Linux会用重定向指定的文件来替换标准输入文件描述符,示例:

cat < testfile.log

使用重定向符号(>)时,Linux会用重定向指定的文件来替换标准输出文件描述符,示例:

ls -l > test.log        标准输出文件描述符替换成test.log,并且是重写模式
ls -l >> test.log       标准输出文件描述符替换成test.log,并且是追加模式

重定向错误

只重定向错误,可以这样(STDERR的文件描述符是2,该值必须紧紧地放在重定向符号前):

ls -al badfile 2> test4

重定向错误和输出(2>是标准错误流重定向,1>是标准输出流重定向),示例:

ls -al test test2 badtest 2> test1.log 1> test2.log

将错误流和输出流同时重定向到一个文件,可以使用特殊的重定向符号&>,示例如下:

ls -al test test2 badtest &> test.log

在脚本中重定向输出

在脚本中重定向输出有两种方式:临时重定向,永久重定向

临时重定向

示例:

#!/bin/bash
echo "This is an error" >&2
echo "This is normal output"

上述脚本将第一行重定向到了错误流,如果在执行该脚本时重定向错误流,即可看到该行是错误流的输出(执行脚本时重定向错误流的原因是默认情况下STDERR会导向STDOUT)

永久重定向

可以使用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符,示例:

#!/bin/bash
exec 1>testout.log
ecec 2>testerror.log
echo "This should go to the testout.log file"
echo "this should go to the testerror.log file" >&2

在脚本中重定向输入

可以使用exec命令将STDIN从键盘重定向到其他位置,示例:

exec 0< testfile.log

创建自己的重定向

shell中最多可以有9个文件描述符,3~8均可用作输入或输出重定向

创建输出文件描述符

示例:

#!/bin/bash
exec 3>test3out.log
echo "This should display on the monitor"
echo "This should be stored in the file" >&3

重定向文件描述符

示例,注意看示例中的文本:

#!/bin/bash
exec 3>&1
exec 1>test4out
echo "This should store in the output file"
exec 1>&3
echo "Now things should be back to normal"

这个例子首先将文件描述符3重定向到文件描述符1,也就是STDOUT,这样所有发送给3的输出都将出现在显示器上。第二个exec命令将STDOUT重定向到文件(但是文件描述符3仍然指向STDOUT原来的位置),所以第一个echo的句子会存入文件中,第三个exec将STDOUT重定向到文件描述符3的当前位置(现在仍然是显示器)。这意味着现在STDOUT又指向了它原来的位置:显示器。

创建输入文件描述符

可以用和上一节通用的办法重定向输入文件描述符,示例如下,注意看其中的文本:

#!/bin/bash
exec 6<&0
exec 0< testfile.log
count=1
while read line
do
    echo "Line #$count: $line"
    count=$[ $count + 1]
done
exec 0<&6
read -p "Ary you done now? " answer
case $answer in
Y|y) echo "bye";;
N|n) echo "Sorry,bye";;
esac

上个例子先用6来保存STDIN的位置,然后将STDIN重定向到文件,读完文件后又将STDIN重定向到文件描述符6也就是最初STDIN的位置

创建读写文件描述符

可以对同一个文件进行数据读写,shell会维护一个内部指针,知名在文件中的当前位置,任何读写都会从文件指针上次的位置开始,示例:

#!/bin/bash
exec 3<> testfile
read line <&3
echo "Read: $line"
echo "This is a test line" >&3

脚本执行前testfile内容:

this is the first line.
this is the second line.
this is the third line

执行后testfile内容:

this is the first line.
this is a test line.
this is the third line.

关闭文件描述符

shell脚本退出时回自动关闭文件描述符,但是也可以手动关闭,格式如下:

exec 3>&-

如果调用已经关闭的文件描述符,会得到Bad file descriptor的错误信息。如果在关闭文件描述符后又在脚本中打开了同一个输出文件,这个文件会被替换。

列出打开的文件描述符

lsof命令会显示当前Linux系统上打开的每个文件的有关信息,包括后台运行的所有进程以及登录到系统的任何用户。

选项-p可以指定进程ID(即PID,可以使用特殊环境变量$$表示当前PID),选项-d运行指定要显示的文件描述符编号,选项-a用来对其他两个选项的结果执行布尔AND运算,示例:

lsof -a -p $$ -d 0,1,2

上栗显示了当前进程(bash shell)的默认文件描述符(0、1和2)。lsof的默认输出信息见下表

描述
COMMAND 正在运行的命令名的前9个字符
PID 进程的PID
USER 进程属主的登录名
FD 文件描述符号以及访问类型(r读,w写,u读写)
TYPE 文件类型(CHR字符型,BLK块型,DIR目录,REG常规文件)
DEVICE 设备的设备号(主设备号和从设备号)
SIZE 如果有的话,表示文件大小
NODE 本地文件的节点号
NAME 文件名

阻止命令输出

/dev/null不存在也不保存任何内容,可以使用他来重定向输入输出流

避免输出错误消息示例:

ls -al badfile test1 2> /dev/null

也可以用它删除文件中的数据:

cat /dev/null > testfile.log

创建临时文件

可以使用mktemp命令创建只有自己可以访问的临时文件,只需要指定文件名,并在文件名末尾加上几个X即可(需要大于3个),ketemp命令会用随机字符替换这几个X,并且确保它在该目录下是唯一的

创建本地临时文件

格式如下:

mktemp test.XXXXXX

因为它会返回创建的文件名,所以可以在shell中使用,并将创建的文件名存入变量中,示例如下:

#!/bin/bash
tempfile=$(mktemp test.XXXXXX)
exec 3>$tempfile
echo "This script writes to temp file $tempfile"
echo "This is the first line" >&3
echo "This is the second line" >&3
exec 3>&-
rm -f $tempfile 2> /dev/null

在/tmp目录创建临时文件

可以使用-t选项来强制mktemp命令在系统的临时目录来创建该文件,这时这个命令返回临时文件的全路径,示例如下:

mktemp -t test.XXXXXX

创建临时目录

可以使用-d选项告诉mktemp命令来创建一个临时目录而不是临时文件,示例如下:

mktemp -d dir.XXXXX

记录消息

tee命令相当于管道的一个T型接头。它将从STDIN过来的数据同时发往两处,一处是STDOUT,另一处是tee命令行所指定的文件名,格式如下:

tee filename

默认情况tee命令会覆盖文件,如果需要追加,则需要使用-a选项,示例如下:

date | tee -a testfile.log

实例

读取.csv文件,并输出sql语句到文本文件中,脚本如下:

#!/bin/bash
outfile='members.sql'
IFS=','
while read lname fname address city state zip
do
    cat >> $outfile << EOF
    INSERT INTO members (lname,fname,address,city,state,zip) VALUES ('$lname','$fname','$address','$city','$state','$zip');
EOF
done < ${1}

共有3个重定向,done后面的重定向知名了待读取数据的文件;cat >> outfile<<EOFcat outfile,而EOF符号标记了追加到文件中的数据的起止

第十六章、控制脚本

处理信号

生成信号

Ctrl+C组合键会生成SIGINT信号,中断进程

Ctrl+Z组合键会生成SIGTSTP信号,暂停进程

ps命令将已暂停作业的状态显示为T

捕获信号

可以使用trap命令捕获信号,格式如下:

trap commands signals

使用示例,可以检测到传入的信号并做处理,这个例子中,捕获到中断信号,但并不退出:

#!/bin/bash
trap "echo ' Sorry! I have trapped Ctrl-c'" SIGINT
other commands

捕获脚本退出

要在脚本退出时进行捕获,可以捕获EXIT信号,示例:

#!/bin/bash
trap "echo bye!" EXIT
other commands

修改或移除捕获

修改捕获,只需要重新进行捕获:

#!/bin/bash
trap "echo ' Sorry! I have trapped Ctrl-c'" SIGINT
other commands
trap "echo ' I modified the trap '" SIGINT
other commands

删除捕获,只需要在trap与信号列表间加两个破折号(单破折号也可以):

#!/bin/bash
trap "echo ' Sorry! I have trapped Ctrl-c'" SIGINT"
other commands
trap -- SIGINT
other commands

以后台模式运行脚本

只需要在命令后加个&符号即可,示例:

./test4.sh &

在非控制台下运行脚本

如果想在终端结束后依然运行命令,可以使用nohup命令,示例:

nohup ./test1.sh &

作业控制

查看作业

脚本可以使用$$变量来显示系统分配给该脚本的PID,示例:

#!/bin/bash
echo "Script Process ID: $$"

jobs命令可以查看分配给shell的后台作业,它有一些参数,例如jobs -l可以显示命令的PID,其他参数如下表:

参数 描述
-l 列出进程的PID及作业号
-n 只列出上次shell发出通知后改变了状态的作业
-p 只列出作业的PID
-r 只列出运行中的作业
-s 只列出已停止的作业

jobs命令输出中的加号会被当做默认作业,在使用控制命令时,如果未指定作业号,则该默认作业会被当做控制命令的操作对象,默认作业完成处理后,带减号的作业成为下一个默认作业。

重启停止的作业

可以使用bg命令将停止的作业从后台启动,示例:

bg      从后台启动默认作业
bg 2    从后台启动作业号为2的作业

可以使用fg命令将停止的作业从前台启动,使用方法和bg相同

调整谦让度

调度优先级是个正数值,从-20(最高优先级)到+19(最低优先级),默认情况下bash shell以优先级0来启动所有进程

nice命令

nice命令可以指定命令启动时的调度优先级,示例如下:

nice -n 10 ./test.sh

renice命令

renice命令可以改变已经运行的命令的优先级,示例如下:

renice -n 10 -p 1234

定时运行作业

用at命令来计划执行作业

默认情况下,at命令将STDIN的输入放到待执行队列中,但是可以用-f来指定用于读取命令的文件名,格式如下:

at [-f filename] time

time参数指定了系统何时运行该作业,如果指定的时间已经错过,at命令会在第二天的那个时间运行该命令。at可以识别多种不同的时间格式,例如:

标准的小时和分钟格式,如10:15
AM/PM指示符,如10:15PM
特定可命名时间,如now、noon、midnight、teatime等
标准日期格式,如MMDDYY、MM/DD/YY或DD.MM.YY
文本日期,如Jul 4或Dec 25,加不加年份均可
你也可以指定时间增量如  +25min

列出等待的作业

使用atq命令可以查看系统中有哪些作业在等待

删除作业

可以使用atrm删除等待中的作业(需要指定队列中的位置,所以要配合atq命令使用),例如:

atrm 18

安排需要定期执行的脚本

可以使用cron命令来安排需要定期执行的脚本,格式如下:

min hour dayofmonth month dayofweek command

cron时间表允许你用特定值,取值范围(如1~5)或者是通配符(星号*)来指定条目。例如,如果想在每天的10:15允许一个命令,可以用cron时间表条目:

15 10 * * * command

构建cron时间表

可以使用crontab -l来列出已有的cron时间表

可以使用crontab -e来修改已有的cron时间表(如果时间表不存在则是一个空文件)

如果脚本对精确的执行时间要求不高,可以用预配置的cron脚本目录,有四个基本目录:/etc/hourly,/etc/daily,/etc/monthly,/etc/weekly,直接把脚本放到相关目录中即可

anacron程序

cron程序是假定系统7*24小时运行的。如果程序关机,则关机过程中会错过一些命令的执行。

anacron之道某个作业错过执行时间时,会尽快运行该作业,如果系统关机了几天,再次开机时,anacron会自动运行在关机期间应该运行的作业。anacron只会处理位于cron目录中的程序,例如/etc/cron.monthly(不会处理时间需求小于一天的脚本,即/etc/cron.hourly)。它用时间戳来确定作业是否执行过,时间戳文件位于/var/spool/anacron。

格式:

period delay identifier command
period指定作业多久运行一次,单位是天
delay指定系统启动后anacron需要等待多少分钟再开始运行错过的脚本

第三部分、高级shell脚本编程

第十七章、创建函数

基本的脚本函数

创建函数格式:

function name {
    commands
}

或者:

name() {
    commands   
}    

使用函数示例:

#!/bin/bash
function func1 {
    echo "hello"  
}    
func1

注意:函数要先定义才可以使用;函数重复定义的话后面会把前面覆盖。

返回值

默认退出状态码

默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码(即使中间有命令失败也不影响)。在函数执行结束后,可以使用$?变量来确定函数的退出状态码,示例如下:

#!/bin/bash
func() {
    echo "hello"
}
func
echo "The exit status is : $?"

使用return命令

return命令可以指定一个退出状态码,但是必须在0~255之间,否则会产生一个错误值(不会报错)

使用函数输出

打破return的限制,可以把函数的输出保存到变量中使用,例如:

#!/bin/bash
function something {
    echo 500
}
result=${something}
echo "The value is $result"

在函数中使用变量

向函数传递参数

bash shell会将函数当做小型脚本对待,所以可以像向普通脚本传递参数一样操作。函数不能直接使用所在脚本的参数(即外部调用脚本时传入的参数),如果要使用必须手动传入。

在函数中处理变量

变量有两种,全局变量和局部变量。默认情况下在脚本中定义的变量都是全局变量,即使在函数中也可以访问。如果函数中需要自己的局部变量,需要在声明变量时加上local关键字,例如:

local temp="hello"

数组变量和函数

向函数传递数组

将数组变量当做单个参数传递的话不会起作用,函数会只取数组变量的第一个值,解决办法是把他们分解成单个值传入,函数中再将其组合,例如:

#!/bin/bash
#
function testit {
    local newarray
    newarray=(;'echo "$@"')
    echo "The new array value is : ${newarray[*]}"
}
myarray=(1 2 3 4 5)
echo "The original array is ${myarray[*]}"
testit ${myarray[*]}

从函数返回数组

从函数里向shell脚本传回数组变量也需要用类似的方法。

创建库

首先要创建库文件myfuncs,例如:

#!/bin/bash
function addem {
    echo $[ $1 + $2 ]
}

在其他脚本中使用则需要用source命令(source命令有个快捷的别名,称作点操作符),例如:

#!/bin/bash
. ./myfuncs
addem 1 4

其中的.可以替换成source命令,是一样的。

source命令是在当前shell上下文中执行命令,而不是创建一个新shell

在命令行上使用函数

在命令行上创建函数

可以直接在命令行创建函数,但是退出shell就会消失

在.bashrc文件中定义函数

两种方式,一是直接在这个文件中创建函数,二是在这个文件中读取其他函数文件(也就是source创建好的函数库文件)

第十八章、图形化桌面环境中的脚本编程

这章并不太感兴趣,没有做笔记,只零散记录了命令

clear

clear命令用于清除屏幕

第十九章、初识sed和gawk

文本处理

sed编辑器

sed编辑器被称作流编辑器,和普通的交互式文本编辑器恰好相反。流编辑器基于预先提供的一组规则来编辑数据流。sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储在文件中。sed编辑器会执行下列操作:

1、一次从输入中读取一行数据
2、根据所提供的编辑器命令匹配数据
3、按照命令修改流中的数据
4、将新的数据输出到STDOUT

sed格式如下:

sed options script file

options选项允许修改sed命令的行为,可使用的选项见下表:

选项 描述
-e script 在处理输入时,将script中指定的命令添加到已有的命令中
-f file 在处理输入时,将file中指定的命令添加到已有的命令中
-n 不产生命令输出,使用print命令来完成输出
在命令行定义编辑器命令

简单示例,将输入流中的test修改成big test,示例:

echo "This is a test" | sed 's/test/big test/'

上述例子使用了s命令,该命令会用斜线间指定的第二个文本字符串来替换第一个文本字符串。

对整个文件执行sed命令的例子:

sed 's/dog/cat/' data1.txt
在命令行使用多个编辑器命令

要在sed命令行上执行多个命令时,只要用-e选项即可,命令间需要用分号隔开,示例:

sed -e 's/brown/green/; s/dog/cat/' data1.txt

如果不想用分号,也可以使用bash shell中的次提示符来分隔命令。只要输入第一个单引号标示出sed程序的起始,bash会继续提示你输入更多命令,直到输入了标示结束的单引号。

从文件中读取编辑器命令

如果有大量要处理的sed命令,可以将它们放进一个单独的文件中更方便一些,可以使用-f选项来指定文件,例子如下:

cat script1.sed
s/brown/green/
s/fox/elephant/
s/dog/cat/

sed -f script1.sed data1.txt

gawk程序

gawk让流编辑迈上了一个新的台阶,它提供了一种编程语言而不只是编辑器命令,在gawk编程语言中,你可以做下面的事情:

定义变量来保存数据
使用算术和字符串操作符来处理数据
使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑
通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告
gawk命令格式

基本格式如下:

gawk options program file

可用选项如下表:

选项 描述
-F fs 指定行中划分数据字段的字段分隔符
-f file 从指定的文件中读取程序
-v var=value 定义gawk程序中的一个变量及其默认值
-mf N 指定要处理的数据文件中的最大字段数
-mr N 指定数据文件中最大数据行数
-W keyword 指定gawk的兼容模式或警告登记
从命令行读取程序脚本

gawk程序脚本用一对花括号来定义,并且还必须将脚本放在单引号中,示例:

gawk '{print "Hello world"}'

执行这个命令后,无论在命令行输入什么,都会输出Hello world,停止程序需要使用Ctrl+D来生成一个EOF字符

使用数据字段变量

gawk会自动给一行中的每个数据元素分配一个变量:

$0代表整个文本行
$1代表文本行中的第1个数据字段
$2代表文本行中的第2个数据字段
$n代表文本行中的第n个数据字段

gawk在读取一行文本时,默认的字段分隔符是任意的空白字符(空格或制表符等),示例:

gawk '{print $1}' data2.txt

上例子会读取data2.txt文件每行的第一个单词

可以使用-F选项指定分隔符,例如指定冒号为分隔符,示例:

gawk -F: '{print $1}' /etc/passwd
在程序脚本中使用多个命令

要在命令行上的程序脚本中使用多条命令,只要在命令之间放歌分号即可,示例:

echo "My name is Rich" | gawk '{$4="Christine"; print $0}'
从文件中读取程序

和sed一样,gawk也可以将程序存储到文件中,在命令行引用,示例:

cat script2.gawk
{
text = "'s home directory is "
print $1 text $6
}

gawk -F: -f script2.gawk /etc/passwd

另外,上例子使用了变量来存放字符串,而引用时并未像shell脚本一样使用美元符。

在处理数据前运行脚本

可以使用BEGIN指定读取数据前预先执行的脚本,示例如下:

gawk 'BEGIN {print "The data3 File Contents:"}{print $0}' data3.txt

上例第一段代码块在读取文件前执行,第二段代码块在读取每一行数据时执行

在处理数据后运行脚本

和BEGIN关键字类似,END关键字指定在读完数据后执行的脚本,示例如下:

gawk 'BEGIN {print "Start of File"}
{print $0}
END {print "End of File"}' data3.text

sed编辑器基础

更多的替换选项

替换标记

替换命令默认情况下只会替换每行中出现的第一处,要让其可以替换一行中不同地方的文本需要使用替换标记,格式如下:

s/pattern/replacement/flags
有四种可用的替换标记(flag):
数字,表示替换匹配到的第几处
g,表明替换所有匹配的文本
p,打印出来
w file,将替换的结果写到文件中

举例如下:

sed 's/test/trial/2' data4.txt          将data4.txt的第二个test字符串替换成trial
sed 's/test/trial/g' data4.txt          将data4.txt的所有test都替换成trial
sed -n 's/test/trial/p' data4.txt       这将会只输出被修改过的行,因为-n将禁止sed编辑器输出,但p会输出修改过的行
sed 's/test/trial/w test.txt' data5.txt 将会把修改过的行保存在test.txt文件中
替换命令中的字符串分隔符

如果文本中本身包含正斜线,则命令会很混乱,因为命令的字符串间的分隔符也是正斜线,我们可以更改命令中的分隔符,比如改成!,示例如下:

sed 's!/bin/bash!/bin/csh!' /etc/passwd

使用地址

sed命令默认作用于文本的所有行,如果只想作用于某些特定行货某些行,则必须使用行寻址,行寻址有两种:以数字形式表示行区间;用文本模式过滤。两种模式都使用相同的格式来指定地址:

[address]command

也可以将特定地址的多个命令分组:

address {
    command1
    command2
    ...
}
数字方式的行寻址

sed会将文本流中的第一行编号为1,然后继续按顺序为接下来的行分配行号,示例:

sed '2s/dog/cat/' data1.txt          修改第二行
sed '2,3s/dog/cat/' data1.txt        修改第二行至第三行
sed '2,$s/dog/cat/' data1.txt       修改第二行到最后一行,美元符用来代表到最后一行
使用文本模式过滤器

必须用正斜线将要知道的正则表达式pattern封起来,格式如下:

/pattern/command

示例:

sed '/Samantha/s/bash/csh/' /etc/passwd     把包含Samantha的行的bash改成csh
命令组合

如果需要执行多条命令,可以用花括号将多条命令扩起来,示例:

sed '3,${
s/brown/green/
s/lazy/active/
}' data1.txt

删除行

删除行可以使用d命令,示例如下:

sed 'd' data1.txt       全部删除
sed '3d' data1.txt      删除第三行
sed '2,3d' data1.txt    删除第二行到第三行
sed '2,$d' data1.txt    删除第二行到最后一行
sed '/number/d' data1.txt   删除包含number字符串的行

也可以使用两个文本模式来删除某个区间内的行,第一次匹配到第一个模式时会打卡删除功能,直到匹配到第二个模式才关闭删除功能,如果后续的行再次匹配到第一个模式,则会再次打卡删除功能,直到再匹配到第二个模式才会关闭,示例如下:

例如我们有一个文件data3.txt如下:
line 1
line 2
line 3
line 4
line 5
line 1
line 2
line 6
然后我们使用命令:
sed '/1/,/3/d' data3.txt
输出如下:
line 4
line 5
因为第一行满足了第一个模式,打开了删除功能,一直删除到第三行,满足了第二个模式于是关闭了删除功能,第4、5行正常输出,第六行又满足了第一个模式,于是打开了删除功能,直到文本结束都没有满足第二个模式,所以后续的行全部被删除。

插入和附加文本

插入和附加的区别:

插入(insert)命令(i)会在指定行前增加一个新行
附加(append)命令(a)会在指定行后增加一个新行

命令格式如下:

sed '[address]command\new line'

示例如下:

echo "Test line 2" | sed 'i\Test line 1'  在给定行(Test line 2)的前面加入新行Test line 1
echo "Test line 2" | sed 'a\Test line 1'  在给定行的后面加入新行
sed '3i\this is an inserted line.' data5.txt 在给定文件的第三行前插入新行
sed '3a\this is an appended line.' data5.txt 在给定文件的第三行后加入新行
sed '$a\this is an appended line.' data5.txt 在给定文件的最后加入新行
sed '1i\this is an inserted line.' data5.txt 在给定文件的最开始插入新行

再提示一下,sed所有操作不会作用于原始文本,只会作用于输出

修改行

修改(change)命令(c)允许修改数据流中整行文本的内容,它和插入、附加命令的机制一样,必须在sed命令中指定新行,示例如下:

sed '3c\this is a changed line of text.' data.txt   修改data.txt文件的第三行为指定的行
sed '/number/c\this is a changed line of text.' data.txt    修改匹配到numbe的行为指定的行
sed '2,3c\this is a new line of text.' data.txt     将第二行至第三行删除掉,替换成给定的一行(注意不会生成多个指定的行,而只会有一个)

转换命令

转换命令(y)是唯一可以处理单个字符的sed编辑器命令,格式如下:

[address]y/inchars/outchars/

转换命令会对inchars和outchars值进行一对一的映射。inchars中的第一个字符会被转换成outchars中的第一个字符,inchars中的第2个字符会被转换成outchars中的第2个字符,这个过程一直持续到处理完所有映射。使用示例:

sed 'y/123/789/' data.txt 把所有1改成7,所有2改成8,所有3改成9

回顾打印

还有3个命令可以用来打印数据流中的信息:

p命令用来打印文本行
等号(=)用来打印行号
l(小写的L)命令用来列出行

示例:

sed -n '/number/p' data.txt     配合-n,只打印有number的行
sed -n '2,3p' data.txt  配合-n,只打印2~3行的数据
sed '=' data.txt 打印data.txt每一行前打印行号
sed -n 'l' data.txt 可以打印出数据流中的文本和不可打印的ASCII字符(例如\t,转义字符等)

使用sed处理文件

写入文件

格式如下:

[address]w filename

示例:

sed '1,2w test.text' data.txt 将data.txt文件中的1~2行数据写入test.test文件中
从文件中读取

读取命令(r)允许将一根独立文件中的数据插入到数据流中,格式如下:

[address]r filename

示例:

sed '3r data1.txt' data2.txt 在读到data2.txt的第三行后,插入data1.txt的所有内容,再继续读data2.txt的内容

第二十章、正则表达式

正则表达式类型

Linux中有两种正则表达式类型:

POSIX基础正则表达式(basic regular expression, BRE)引擎
POSIX扩展正则表达式(extended regular expression,ERE)引擎

其中,sed使用BRE,gawk使用ERE

BRE模式

数字、英文字母等非特殊字符都是纯文本,区分大小写。特殊字符需要用反斜杠转义

锚字符

锁定行首

^ 以^开始的正则表达式必须匹配在行首,例如:

sed -n '/^book/p' 匹配以book开始的行
锁定行尾

结尾的正则表达式必须匹配在行尾,例如:

sed -n '/book$/p' 匹配以book结束的行
组合锚点

组合以^开始以$结尾的正则表达式必须恰好匹配整行,例如:

sed -n '/^this is a test$/p' data4.txt  匹配文本中内容恰好是this is a test的行

点号字符

. 特殊字符点号用来匹配除换行外的任意单个字符,例如:

sed -n '/.at/p' data6.txt   匹配data6.txt中所有包含【例如cat,hat,eat等以at结尾的模式】的行

字符组

使用方括号来定义一个字符组,使用中只要字符组中的任意一个字符匹配即可,例如:

sed -n '/[ch]at/p' data.txt 匹配data.txt中所有包含cat或hat的行

排除型字符组

脱字符^可以翻转字符组的作用,即寻找组中没有的字符,而不是去寻找组中有的字符,例如:

sed -n '/[^ch]at/p' data.txt    匹配data.txt中所有不包含cat不包含hat的行

区间

字符组中冶可以使用区间,只需要指定区间的第一个字符、单破折号以及区间的最后一个字符,例如:

[0-9]   匹配0~9区间中的数字
[a-z]   匹配a~z区间中的字母
[a-ch-m] 匹配a~c区间的字母或h~m区间的字母

特殊字符组

BRE还包含了一些特殊的字符组,可以匹配特定类型的字符,列举如下:

描述
[[:alpha:]] 匹配任意字母字符,不管是大写还是小写
[[:alnum:]] 匹配任意字母或数字字符
[[:blank:]] 匹配空格或制表符
[[:digit:]] 匹配0~9直接的数字
[[:lower:]] 匹配小写字母字符
[[:print:]] 匹配任意可打印字符
[[:punct:]] 匹配标点符号
[[:space:]] 匹配任意空白字符:空格、制表符、NL、FF、VT和CR
[[:upper:]] 匹配任意大写字母字符

星号

星号表示该字符在匹配模式的文本中出现0次或多次

ERE 扩展正则表达式

问号

前面的字符出现0次或1次

加号

前面的字符出现1次或多次

使用花括号

ERE中的花括号允许为可重复的正则表达式指定一个上限。通常称为间隔(interval)。可以用两种格式来指定区间:

m: 正则表达式准确出现m次
m,n:正则表达式至少出现m次,至多n次

示例:

echo "bet" | gawk --re-interval '/be{1}t/{print $0}'    匹配bet;gawk必须使用--re-interval才可以支持花括号的模式
echo "bet" | gawk --re-interval '/be{1,2}t/{print $0}'    匹配bet或beet;gawk必须使用--re-interval才可以支持花括号的模式

管道符号

管道符号可以在检查数据流时用逻辑OR的方式指定正则表达式引擎要用的多个模式,如果任何一个模式匹配,文本就通过测试,示例如下:

echo "The cat is asleep" | gawk '/cat|dog/{print $0}'       匹配cat模式或dog模式

表达式分组

正则表达式模式也可以用圆括号进行分组。将正则表达式模式分组时,该组会被视为一个标准字符。可以像对普通字符一样给该组使用特殊字符,例如:

echo "Sat" | gawk '/Sat(urday)?/{print $0}'   会将urday当做一个字符来使用特殊字符?,即urday出现0次或1次,也就是匹配Sat或Saturday

第二十一章、sed进阶

多行命令

所有的sed编辑器命令都是针对单行数据执行操作的。sed编辑器包含了三个可用来处理多行文本的特殊命令:

N:将数据流中的下一行加进来创建一个多行组来处理
D:删除多行组中的一行
P:打印多行组中的一行

next 命令

单行的next命令

小写的n命令会告诉sed编辑器移动到数据流中的下一行文本,例如:

sed '/header/{n ; d}' data.txt  查找含有单词header的那行,找到之后,n命令会让sed编辑器移动到文本的下一行,然后删除那一行。也就是说这个命令删除包含header那行的下一行数据
合并文本行

单行next命令会将数据流中的下一文本行移动到sed编辑器的工作空间(称为模式空间)。多行版本的next命令(N)会将下一文本行添加到模式空间中已有的文本后。这样的作用是将数据流中的两个文本行合并到同一个模式空间中。示例:

sed '/first/{ N ; s/\n/ / }' data2.txt 查找含有first的哪行文本,找到后,用N命令将下一行合并到那行,然后用替换命令s将换行符替换成为空格。

另一个例子:

sed 'N ; s/system.administrator/desktop user/' data.txt 替换命令在system和administrator之间使用了通配符模式(.)来匹配空格和换行符

上面例子中,当它到了最后一行文本时,就没有下一行可读了,所以N命令会叫sed编辑器停止。要解决这个办法,可以将单行命令放到N命令前面,并将多行命令放到N命令后面,例如:

sed '
s/system administrator/desktop user/
N
s/system\nadministrator/desktop\nuser/
' data4.txt

多行删除命令

sed 'N ; /systme\n/administrator/d' data4.txt   删除命令会在不同的行中查找单词System和administrator,然后再模式空间中将两行都删掉。

sed为编辑器提供了多行删除命令D,它只删除模式空间中的第一行,例如:

sed 'N ; /systme\n/administrator/D' data4.txt   删除命令会在不同的行中查找单词System和administrator,然后再模式空间中删除第一行。

多行打印命令

多行打印命令(P)沿用了同样的方法。它只打印多行模式空间中的第一行。这包括模式空间中直到换行符为止的所有字符。例如:

sed -n 'N ; /System\nAdministrator/P' data3.txt     当多行匹配出现时,P命令只会打印模式空间中的第一行

保持空间

模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时它会保存待检测的文本。但它并不是sed编辑器保存文本的唯一空间。sed编辑器有另一块称作保持空间(hold space)的缓冲区域。在处理模式空间中的某些行时,可以用保持空间来临时保存一些行。有5条命令可用来操作保持空间,见下表:

命令 描述
h 将模式空间复制到保持空间
H 将模式空间附加到保持空间
g 将保持空间复制到模式空间
G 将保持空间附加到模式空间
x 交换模式空间和保持空间的内容

给出一个例子,有文件data2.txt:

This is the header line.
this is the first data line.
this is the second data line.
this is the last line.

使用命令:

sed -n '/first/ {h ; p ; n ; p ; g ; p}'

输出为:

this is the first data line.
this is the second data line.
this is the first data line.

具体过程为,将含有first的行用h命令放到保持空间,并打印该行,读取下一行并打印,用g命令取回保持空间的行并打印

排除命令

感叹号命令(!)用来排除命令,也就是让原本会起作用的命令不起作用。例如:

sed -n '/header/!p' data2.txt   除了包含header那一行外,文件中其他所有的行都被打印出来了

倒序打印文件的例子:

sed -n '{1!G ; h ; $p }' data2.txt  如果不是第一行,则把保持空间的内容追加到模式空间,然后将模式空间内容保存到保持空间,如果是最后一行,则输出模式空间

改变流

分支

sed编辑器提供了一种方法,可以基于地址、地址模式或地址区间排除一整块命令。这允许你只对数据流中的特定行执行一组命令。

分支(branch)命令b的格式如下:

[address]b [label]

address参数决定了哪些行的数据会触发分支命令。label参数定义了要跳转到的位置。如果没有加label参数,跳转命令会跳转到脚本的结尾。示例:

sed '{2,3b ; s/This is /Is this/ ; s/line./test?/}' data2.txt   分支命令在数据流中的第二行和第三行处跳过了两个替换命令

带有label的例子:

sed '{/first/b jump1 ; s/This is the/No jump on/
:jump1
s/This is the/Jump here on/}' data2.txt

上例中跳转命令指定如果文本行中出现了first,程序应该调到标签为jump1的脚本行。

再来一个循环的例子,这个例子循环地删除句子中的逗号:

echo "this, is, a, test, to, remove, commas." | sed -n '{
:start
s/,//1p
b start
}'

这个例子每次迭代都会删除文本中的一个逗号,并打印字符串,但是这个例子不会结束,因为他一直在循环。

测试

测试(test)命令(t)会根据替换命令的结果跳转到某个标签,而不是根据地址进行跳转。如果替换命令成功匹配并替换了一个模式,测试命令就会跳转到指定的标签。如果替换命令未能匹配指定的模式,测试命令就不会跳转,格式如下:

[address]t [label]

例子如下:

sed '{
s/first/matched/
t
s/this is the/no match on/
}' data2.txt

第一个替换命令会查找模式文本first。如果匹配了并且替换了文本,测试命令会跳过后续的替换命令。

上一节会无限循环的例子可以使用测试来使其能够退出:

echo "this, is, a, test, to, remove, commas." | sed -n '{
:start
s/,//1p
t start
}'

模式替代

&符号

&符号可以用来替换命令行中的匹配的模式,例如:

echo "the cat sleeps in his hat." | sed 's/.at/"&"/g'
输出为:
the "cat" sleeps in his "hat".

替代单独的单词

&会提前模式匹配到的整个字符串,有时我们只想提取这个字符串的一部分。

sed编辑器用圆括号来定义替换模式中的子模式。你可以在替代模式中使用特殊字符来引用每个子模式。替代字符由反斜线和数字组成。数字说明子模式的位置。sed编辑器会给第一个子模式分配字符\1,给第二个子模式分配字符\2,依次类推。

当在替换命令中使用圆括号时,必须用转义字符将它们表示为分组字符而不是普通的圆括号。这跟转义其他特殊字符正好相反。

例子:

echo "the system administrator mannul" | sed '
s/\(system\) administrator/\1 user/'
输出:
the system user mannul

这个替换命令用一对圆括号将单词system扩起来,将其表示为一个子模式。然后再替代模式中使用\1来提取第一个匹配的子模式。

创建sed实用工具

加倍行间距

sed 'G' /etc/passwd 加倍行间距
sed '$!G' /etc/passwd 除最后一行外,加倍行间距

G命令会简单地将保持空间内容附加到模式空间内容后。当启动sed编辑器时,保持空间只有一个空行。

对可能含有空白行的文件加倍行间距

sed '/^$/d ; $!G' /etc/passwd 在上一个例子的基础上,增加了删除空白行/^$/d

给文件中的行编号

sed '=' /etc/passwd | sed 'N; s/\n/ /'

有些bash shell命令也可以添加行号,例如:

nl /etc/passwd
cat -n /etc/passwd

打印末尾行

sed -n '$p' /etc/passwd

下面是输出末尾10行的例子:

sed '{
:start
$q ; N ; 11,$D
b start
}' /etc/passwd

这个脚本首先检查这行是不是数据流中最后一行,如果是,退出循环。N命令将下一行附加到模式空间中当前行之后。如果当前行在第10行后面,11,$D命令会删除模式空间中的第一行。这就会在模式空间中创建出滑动窗口效果。因此这个程序只显示最后10行。

删除行

删除连续的空白行
sed '/./,/^$/!d' data.txt   区间是/./到/^$/。区间的开始地址会匹配任何含有至少一个字符的行。区间的结束地址会匹配一个空行。在这个区间内的行不会被删除。
删除开头的空白行
sed '/./,$!d' data.txt    这个区间从含有字符的行开始,一直到数据流结束。在这个区间内的任何行都不会从输出中删除
删除结尾的空白行
sed '{
:start
/^\n*$/{$d; N; b start }
}' test.log

在正常的花括号里还有花括号。这允许你在整个命令脚本中将一些命令分组。地址模式匹配只含有换行符的行。如果找到了这样的行,而且还是最后一行,删除命令会删掉它,如果不是最后一行,N命令将下一行附加到它后面,分支命令会调到循环起始位置重新开始。

删除HTML标签

sed 's/<[^>]*>//g ; /^$/d' data.txt

gawk进阶

使用变量

gawk编程语言支持两种不同类型的变量:内建变量 ;自定义变量

内建变量

gawk程序使用内建变量来引用程序数据里的一些特殊功能。

字段和记录分隔符变量

数据字段变量。数据字段变量允许你使用美元符号( 1;要引用第二个字段,就用$2,依次类推。

数据字段是由字段分隔符来划定的。默认情况下,字段分隔符是一个空白字符,也就是空格符或者制表符。

下面这组变量用于控制gawk如何处理输入输出数据中的字段和记录:

变量 描述
FIELDWIDTHS 由空格分隔的一列数字,定义了每个数据字段确切宽度
FS 输入字段分隔符
RS 输入记录分隔符
OFS 输出字段分隔符
ORS 输出记录分隔符

默认情况下,gawk将OFS 设成一个空格,例如:

$ cat data1 
data11,data12,data13,data14,data15 
data21,data22,data23,data24,data25 
data31,data32,data33,data34,data35 
$ gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1 
data11 data12 data13 
data21 data22 data23 
data31 data32 data33

可以修改OFS,例如:

$ gawk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1 
data11-data12-data13 
data21-data22-data23 

一旦设置了FIELDWIDTH变量,gawk就会忽略FS变量,并根据提供的字段宽度来计算字段。例如:

$ cat data1b 
1005.3247596.37 
115-2.349194.00 
05810.1298100.1 
$ gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b 
100 5.324 75 96.37 
115 -2.34 91 94.00 
058 10.12 98 100.1

一旦设定了FIELDWIDTHS 变量的值,就不能再改变了。这种方法并不适用于变长的字段。

数据变量

gawk中的其他内建变量:

变量 描述
ARGC 当前命令行参数个数
ARGIND 当前文件在ARGV中的位置
ARGV 包含命令行参数的数组
CONVFMT 数字的转换格式(参见printf语句),默认值为%.6 g
ENVIRON 当前shell 环境变量及其值组成的关联数组
ERRNO 当读取或关闭输入文件发生错误时的系统错误号
FILENAME 用作gawk输入数据的数据文件的文件名
FNR 当前数据文件中的数据行数
IGNORECASE 设成非零值时,忽略gawk命令中出现的字符串的字符大小写
NF 数据文件中的字段总数
NR 已处理的输入记录数
OFMT 数字的输出格式,默认值为%.6 g
RLENGTH 由match 函数所匹配的子字符串的长度
RSTART 由match 函数所匹配的子字符串的起始位置

自定义变量

gawk自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,要记住gawk变量名区分大小写。

在脚本中给变量赋值
$ gawk ' 
> BEGIN{ 
> testing="This is a test" 
> print testing 
> }'

赋值语句还可以包含数学算式来处理数字值,例如:

gawk 'BEGIN{x=4; x= x * 2 + 3; print x}' 
在命令行上给变量赋值
$ cat script1 
BEGIN{FS=","} 
{print $n} 
$ gawk -f script1 n=2 data1 
data12 
data22 
data32 
$ gawk -f script1 n=3 data1 
data13 
data23 
data33

使用命令行参数来定义变量值会有一个问题。在你设置了变量后,这个值在代码的BEGIN部分不可用。可以用-v命令行参数来解决这个问题。它允许你在BEGIN 代码之前设定变量,例如:

gawk -v n=3 -f script2 data1

处理数组

gawk编程语言使用关联数组提供数组功能。 关联数组跟数字数组不同之处在于它的索引值可以是任意文本字符串。如果你熟悉其他编程语言的话,就知道这跟散列表和字典是同一个概念。

定义数组变量

数组变量赋值的格式如下:

var[index] = element

其中var 是变量名,index是关联数组的索引值,element是数据元素值。下面是一些gawk中数组变量的例子:

capital["Illinois"] = "Springfield" 
capital["Indiana"] = "Indianapolis"

引用数组变量例子:

$ gawk 'BEGIN{ 
> capital["Illinois"] = "Springfield" 
> print capital["Illinois"] 
> }'

遍历数组变量

for (var in array) 
{ 
  statements 
} 

删除数组变量

格式如下:

delete array[ index] 

例如:

delete var["g"]

使用模式

正则表达式

在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前,例如:

gawk 'BEGIN{FS=","} /11/{print $1}' data1

匹配操作符

匹配操作符(matching operator)允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线(~)。示例如下:

gawk 'BEGIN{FS=","} $2 ~ /^data2/{print $0}' data1

上例中,匹配操作符会用正则表达式/^data2/ 来比较第二个数据字段,该正则表达式指明字符串要以文本data2开头。

你也可以用!符号来排除正则表达式的匹配,例如:

gawk –F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd

数学表达式

除了正则表达式,你也可以在匹配模式中用数学表达式,例如:

gawk -F: '$4 == 0{print $1}' /etc/passwd 

可以使用任何常见的数学比较表达式:

x == y :值x等于y。 
x <= y :值x小于等于y。 
x < y:值x小于y。 
x >= y :值x大于等于y。 
x > y:值x大于y。 

也可以对文本数据使用表达式,但必须小心。跟正则表达式不同,表达式必须完全匹配。

结构化命令

if语句

你必须为if语句定义一个求值的条件,并将其用圆括号括起来。格式如下:

if (condition) 
statement1 

或者也可以使用一行,例如:

if (condition) statement1 

示例如下:

gawk '{if ($1 > 20) print $1}' data4

如果需要在if语句中执行多条语句,就必须用花括号将它们括起来。并且支持else语句,示例如下:

$ gawk '{ 
> if ($1 > 20) 
> { 
>    x = $1 * 2 
>    print x 
> } else 
> { 
>    x = $1 / 2 
>    print x 
> }}' data4

可以在单行上使用else子句,但必须在if语句部分之后使用分号,格式如下:

if (condition) statement1; else statement2 

while 语句

while (condition) 
{ 
  statements 
}

gawk编程语言支持在while 循环中使用break语句和continue 语句

do-while 语句

do 
{ 
  statements 
} while (condition) 

这种格式保证了语句会在条件被求值之前至少执行一次。

for 语句

gawk编程语言支持C风格的for循环。格式如下:

for( variable assignment; condition; iteration process)

示例如下:

$ gawk '{ 
> total = 0 
> for (i = 1; i < 4; i++) 
> { 
>    total += $i 
> } 
> avg = total / 3 
> print "Average:",avg 
> }' data5

格式化打印

格式化打印命令,叫作printf,格式:

printf " format string ",  var1, var2 . . .

格式化指定符采用如下格式:

%[modifier ] control- letter  

其中control-letter是一个单字符代码,用于指明显示什么类型的数据,而modifier则定义了可选的格式化特性。下表列出了可用在格式化指定符中的控制字母:

控制字母 描述
c 将一个数作为ASCII字符显示
d 显示一个整数值
i 显示一个整数值(跟d一样)
e 用科学计数法显示一个数
f 显示一个浮点值
g 用科学计数法或浮点数显示(选择较短的格式)
o 显示一个八进制值
s 显示一个文本字符串
x 显示一个十六进制值
X 显示一个十六进制值,但用大写字母A~F

使用示例:

$ gawk 'BEGIN{ 
> x = 10 * 100 
> printf "The answer is: %e\n", x 
> }' 
The answer is: 1.000000e+03

除了控制字母外,还有3种修饰符可以用来进一步控制输出:

width:指定了输出字段最小宽度的数字值。如果输出短于这个值,printf会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
prec:这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的最大字符数。 
- (减号):指明在向格式化空间中放入数据时采用左对齐而不是右对齐。

下面给出一个例子,通过添加一个值为16的修饰符,我们强制第一个字符串的输出宽度为16个字符,再给修饰符加一个减号改成左对齐:

gawk 'BEGIN{FS="\n"; RS=""} {printf "%-16s  %s\n", $1, $4}' data2 

内建函数

数学函数

函数 描述
atan2(x, y) x/y的反正切,x和y以弧度为单位
cos(x) x的余弦,x以弧度为单位
exp(x) x的指数函数
int(x) x的整数部分,取靠近零一侧的值
log(x) x的自然对数
rand( ) 比0大比1 小的随机浮点值
sin(x) x的正弦,x以弧度为单位
sqrt(x) x的平方根
srand(x) 为计算随机数指定一个种子值

除了标准数学函数外,gawk还支持一些按位操作数据的函数。

and(v1, v2) :执行值v1和v2的按位与运算。 
compl( val ) :执行val 的补运算。 
lshift(val , count) :将值val 左移count位。 
or( v1, v2) :执行值v1和v2的按位或运算。 
rshift(val , count) :将值val 右移count位。 
xor(v1, v2) :执行值v1和v2的按位异或运算。 

字符串函数

函数 描述
asort( s [, d ]) 将数组s按数据元素值排序。索引值会被替换成表示新的排序顺序的连续数字。另外,如果指定了d,则排序后的数组会存储在数组d中
asorti(s [, d ]) 将数组s按索引值排序。生成的数组会将索引值作为数据元素值,用连续数字索引来表明排序顺序。另外如果指定了d,排序后的数组会存储在数组d中
gensub(r , s , h [, t ]) 查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果h是一个以g或G 开头的字符串,就用s替换掉匹配的文本。如果h是一个数字,它表示要替换掉第h处r匹配的地方
gsub(r , s [, t ]) 查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果找到了,就全部替换成字符串s
index( s , t ) 返回字符串t在字符串s中的索引值,如果没找到的话返回0
length([ s ]) 返回字符串s的长度;如果没有指定的话,返回$0的长度
match( s , r [, a ]) 返回字符串s中正则表达式r 出现位置的索引。如果指定了数组a,它会存储 s中匹配正则表达式的那部分
split( s , a [, r ]) 将s 用FS字符或正则表达式r (如果指定了的话)分开放到数组a中。返回字段的总数
sprintf( format , variables) 用提供的format和variables返回一个类似于printf输出的字符串
sub(r , s [, t ]) 在变量$0或目标字符串t 中查找正则表达式r 的匹配。如果找到了,就用字符串s 替换掉第一处匹配
substr(s , i [, n ]) 返回s 中从索引值i 开始的n 个字符组成的子字符串。如果未提供n ,则返回s 剩下的部分
tolower( s ) 将s中的所有字符转换成小写
toupper( s ) 将s中的所有字符转换成大写

时间函数

函数 描述
mktime(datespec ) 将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值
strftime(format [,timestamp]) 将当前时间的时间戳或timestamp(如果提供了的话)转化格式化日期(采用shell函数date()的格式)
systime( ) 返回当前时间的时间戳

自定义函数

定义函数

function name([variables]) 
{ 
    statements 
}

函数还能用return语句返回值:

return value 

举例如下:

function myrand(limit) 
{ 
   return int(limit * rand()) 
} 

使用自定义函数

在定义函数时,它必须出现在所有代码块之前(包括BEGIN代码块)。

创建函数库

首先,你需要创建一个存储所有gawk函数的文件。要使用库,只要创建一个含有你的gawk程序的文件,然后在命令行上同时指定库文件和程序文件就行了,例如:

gawk -f funclib -f script4 data2

你要做的是当需要使用库中定义的函数时,将funclib 文件加到你的gawk命令行上就可以了。

实例

比赛得分情况分析示例,我们有各队得分的文件:

$ cat scores.txt 
Rich Blum,team1,100,115,95 
Barbara Blum,team1,110,115,100 
Christine Bresnahan,team2,120,115,118 

脚本如下:

$ cat bowling.sh 
#!/bin/bash 
for team in $(gawk –F, '{print $2}' scores.txt | uniq) 
do 
   gawk –v team=$team 'BEGIN{FS=","; total=0} 
   { 
      if ($2==team) 
      { 
         total += $3 + $4 + $5; 
      } 
   } 
   END { 
      avg = total / 6; 
      print "Total for", team, "is", total, ",the average is",avg 
   }' scores.txt 
done 

for 循环中的第一条语句过滤出数据文件中的队名,然后使用uniq命令返回不重复的队名。for循环再对每个队进行迭代。for 循环内部的gawk语句进行计算操作。对于每一条记录,首先确定队名是否和正在进行循环的队名相符。这是通过利用gawk的-v选项来实现的,该选项允许我们在gawk程序中传递shell变量。如果队名相符,代码会对数据记录中的三场比赛得分求和,然后将每条记录的值再相加,只要数据记录属于同一队。 在循环迭代的结尾处,gawk代码会显示出总分以及平均分。

第四部分、创建实用的脚本

第二十四章、编写简单的脚本使用工具

浏览后简单记录目录,如果以后有需要再细看

文件归档

管理用户账户

监测磁盘空间

第二十五章、创建与数据库、Web及电子邮件相关的脚本

MySQL数据库

在脚本中使用数据库

mysql程序使用$HOME/.my.cnf 文件来读取特定的启动命令和设置。其中一项设置就是用户启动的mysql会话的默认密码,可以先配置这个。

脚本示例1:

$ cat mtest1 
#!/bin/bash 
# send a command to the MySQL server 

MYSQL=$(which mysql) 

$MYSQL mytest -u test -e 'select * from employees'

脚本示例2:

$ cat mtest2 
#!/bin/bash 
# sending multiple commands to MySQL 

MYSQL=$(which mysql) 
$MYSQL mytest -u test < 40000; 
EOF

脚本示例3:

$ cat mtest3 
#!/bin/bash 
# send data to the table in the MySQL database 

MYSQL=$(which mysql) 

if [ $# -ne 4 ] 
then 
 echo "Usage: mtest3 empid lastname firstname salary" 
else 
 statement="INSERT INTO employees VALUES ($1, '$2', '$3', $4)" 
 $MYSQL mytest -u test << EOF 
 $statement 
EOF 
 if [ $? -eq 0 ] 
 then 
    echo Data successfully added 
 else 
    echo Problem adding data 
 fi 
fi

你可能感兴趣的:(读书笔记)