作者:杨豪迈
man cmd
info cmd
区域号 | 涵盖内容 |
---|---|
1 | 可执行程序或者shell命令 |
2 | 系统调用 |
3 | 库调用 |
4 | 特殊文件 |
5 | 文件格式与约定 |
6 | 游戏 |
7 | 概览,约定与杂项 |
8 | 超级用户与系统管理员命令 |
9 | 内核历程 |
具体用法实例
man 7 hostname
man 1 intro
cd dir
pwd
cd命令用来选择并进入相应的工作目录
pwd从来打印当前的工作目录
ls
tree
ls命令用来显示当前目录的列表
tree命令用来显示当前的目录树
ls命令常用选项
选项 | 用途 |
---|---|
-F | 用来区分目录和文件 |
-a | 同时打印隐藏文件 |
-R | 递归列出子目录 |
-l | 显示文件详细信息 |
-i | 显示文件inode编号 |
something | something为正则表达式,可以过滤输出结果 |
#用来创建文件
touch file
#用来拷贝文件
cp src dest
#可以使用正则表达式
# -i选项用来添加提醒是否覆盖存在文件(默认不提醒)
# -r -R 表示递归拷贝文件树
#创建链接
ln data_files sl_data_file
#如果是软连接,有不同的inode编号,为不同文件
ln -s data_file l_data_file
#如果是硬链接,同一个inode编号,为同一个文件
ln data_file sl_data_file
#移动与重命名文件,不改变时间戳与inode
mv src dest
#也可以移动文件
#删除文件
rm file
# -i 提示你是否真的删除
# -r 递归删除文件夹
# -f 强制删除
#创建目录
mkdir dir
# -p 批量创建子目录
#查看目录树
tree
#删除文件夹
rmdir
#如果目录有文件就无法删除目录,不如rm -rf方便
#查看文件类型
file my_file
#查看文件内容(一次性显示)
cat file
# -n 加上行号
# -b 只给有文本的行加上行号
# -T 忽略制表符
#查看文件内容(分页显示)
more file
less file
#more 和 less命令功能基本相同,其中less支持上下翻页
#查看文件尾部
tail file
# -n 显示末尾的行数,默认10行,例如 -n 5
#查看文件的头部
head file
# -n 需要显示的头部的行数,例如 -n 5
sudo cmd
如果你当前不是root用户,可以通过在命令开始添加sudo来运用root权限执行命令
ps
参数 | 描述 |
---|---|
-A | 显示所有进程 |
-a | 显示除控制进程(session leader)和无中断进程之外的所有进程 |
-d | 显示除控制进程之外的所有进程 |
-e | 同 -A |
-l | 显示长列表 |
-H | 显示进程层次关系 |
-f | 显示完整格式输出 |
–forest | 显示更加清晰的层级关系 |
ps 命令详细介绍见man ps
top
top命令可以对系统当前进程进行cpu占用的排序,键入f可以选择排序字段,键入d可以修改轮询间隔,键入q可以退出top
常用字段介绍
字段 | 解释 |
---|---|
PID | 进程ID |
USER | 进程属主 |
PR | 进程优先级 |
NI | 进程谦让度 |
VIRT | 进程占用虚拟内存总量 |
RES | 进程占用物理内存总量 |
SHR | 进程和其他进程共享内存总量 |
S | 进程当前状态 D:可以中断的休眠 R:运行 S:休眠 T:跟踪或者停止 Z:僵化 |
%CPU | 占用CPU比率 |
%MEM | 占用内存比率 |
kill PID
通过kill -l
查看所有的可以控制的信号,然后可以通过kill -s 信号名称 pid
来向程序发出相关信号,例如kill -s HUP 3940
通常使用kill -9 pid
无条件终止进程
killall name
killall
命令用来根据进程的名称杀死进程,支持通配符
mount
默认情况下,输入mount会显示系统的挂载的设备列表
挂载的通常用法:mount -t type device directory
就可以把设备挂到对应的目录下
常用参数
参数 | 描述 |
---|---|
-a | 挂载/etc/fstab文件中指定的所有文件系统 |
-f | 模拟挂载,但是不真正挂载 |
-v | 详细模式,说明挂载每一步 |
-p | 加密挂载时,从文件描述符num中获取密码 |
-r | 挂载为只读 |
-w | 挂载为可读写 |
-L | 指定label挂载 |
-U | 指定uuid挂载 |
umount dir
umount device
卸载一个移动设备
df
df可以轻松的查看当前你的设备剩余的磁盘空间,一般使用df -h
来以用户易读的形式显示剩余空间
#显示文件占用
du
# -c 显示列出文件的总大小
# -h 按照用户易读的形式显示
# -s 显示每个参数的总计
sort
sort可以对输入进行字符串排序,通过添加-n选项可以转换为数字排序。通常与管道相结合使用(管道后面会介绍)
详细的sort用法参考man sort
grep [options] pattern [file]
grep 是一个常用的强大的搜索工具,也通常与管道相结合使用
如果包含file,就在file中搜索包含pattern正则模式匹配的字符,如果没有file,就通过管道获取数据,搜索字符串
常用选项
选项 | 描述 |
---|---|
-r | 递归在文件夹中搜索 |
-n | 显示查找的行号 |
-v | 表示搜索不包含pattern的行,也就是相反的匹配结果 |
-c | 统计有多少行包含匹配的模式 |
-e | 可以通过包含多个-e来制定多个模式,例如grep -e one_pattern -e another_pattern file |
-a | 不要忽略二进制数据 |
-w | 只显示全字符合的列 |
file也可以使用正则表达式匹配
工具 | 扩展名 |
---|---|
bzip2 | .bz2 |
compress | .Z (基本过时) |
gzip | .gz |
zip | .zip |
.gz可以通过gunzip解压
.zip可以通过unzip解压
示例:
zip -r ret.zip dir
unzip ret.zip -d dir
linux标准归档数据命令是tar命令
tar function [options] object1 object2 ...
选项 | 描述 |
---|---|
-A | 将已有归档追加到另一个已有的归档文件 |
-c | 创建一个新的归档文件 |
-d | 检查归档文件和文件系统中文件的不同 |
–delete | 从归档文件中删除 |
-r | 追加到归档文件末尾 |
-u | 追加新的同名文件 |
-x | 从已有的归档文件中提取文件 |
-C dir | 切换到制定目录 |
-f file | 指定归档的文件名,一般为最后一个参数,指定文件名 |
-p | 保留文件权限 |
-v | 处理文件时显示文件 |
-z | 有gzip属性的 gz |
-j | 有bz2属性的 bz2 |
-J | -J :有xz属性的 xz |
后缀名有.gz属性的添加 -z
后缀名有.bz2属性的添加 -j
后缀名有.xz属性的添加 -J
使用示例:
#创建一个归档文件
tar -cvf test.tar test/ test2/
#查看归档文件内容
tar -tf test.tar
#提取归档文件内容
tar -xvf test.tar
bash
exit
通过输入bash创建子shell,通过输入exit退出当前子shell
通过输入命令ps --forest
查看当前终端中shell的层级关系
进程列表是通过小括号括起来的一组命令,形如:
#下面是进程列表
(pwd;cd test;pwd;echo $BASH_SUBSHELL)
#下面不是进程列表
pwd;cd test;pwd;echo $BASH_SUBSHELL
cmd &
可以通过在命令后面添加&来使命令进入后台运行,前台仍可以输入命令执行。
例如:
sleep 1000 &
#通过jobs命令可以查看当前终端运行的后台命令
jobs
同样,对于前台执行的命令,可以先通过Ctrl-z中断当前控制台执行的命令,然后输入bg
命令把当前中断执行的命令放进后台执行,可以达到与直接放入后台执行相同的结果
也可以使用fg pid
命令把后台命令重新放到前台来执行
协程的创建不经常使用,协程命令会创建一个子bash,并将命令放进子bash中运行
coproc cmd
#例如
coproc sleep 100
输入ps --forest
可以查看bash与命令的层级关系
ctrl-d
命令会在命令终端中输入EOF字符(文件结束符),相当于输入了exit,可以用来退出当前终端
ctrl-z
会暂停当前前台的命令执行(信号SIGSTP),并不是停止当期命令执行。暂停之后可以通过输入bg
将命令放到后台运行,也可以通过fg
回复前台运行
ctrl-c
会中断当前执行的命令。相当于发送SIGINT信号
外部命令是存在于bash之外的系统程序,不属于shell的一部分,通常位于/bin,/usr/bin,/sbin,/usr/sbin
当中
#打印外部命令的位置
which cmd
#打印命令的类型
type cmd
#如果输出是buildin,就是shell的内建命令,如果输出位置,就是外部命令
例如cd exit echo pwd history
等都是内建命令,命令的功能直接储存在shell当中
常用内建命令:
histroy
用来打印最近使用的命令列表,列表储存在.bash_history
当中。
!num
你可以通过!num
来获取前面执行的第num条命令
alias cmd='origin cmd'
可以通过alias给命令添加别名,也可以通过alias -p
来显示当前的所有别名
环境变量就是在shell中用来在内存中储存信息的一种特性
全局变量对于shell会话和所有的子shell都是可见的
printenv
命令可以查看全局变量的值,例如printenv HOME
也可以通过echo $HOME
打印环境变量的值
局部环境变量只能在定义它的进程中可见。
linux没有专门显示局部变量的命令,但是可以使用set命令显示为某个特定进程设置的包括全局变量,局部变量,和用户自定义变量在内的所有变量
my_var=hello
my_var="Hello world"
#注意,等号两边不要有空格,不然会被误当成命令执行
#如果环境变量之间有空格,就需要使用“”将变量包裹
echo $my_var
这样定义的环境变量在子bash和父bash都不可用,只能用于当前的bash进程
g_var=hello
export g_var
(echo $var)
echo $var
通过使用export到出环境变量到全局,该环境变量在子shell中仍可见,但是子shell对父shell中定义的全局环境变量更改不会影响父shell中的全局变量的值,即使子shell导出全局变量也不会对父shell有任何影响
unset my_var
删除环境变量同样不会对父shell产生影响
在PATH环境变量中储存这用户命令的位置,使用:
进行分割,如果添加路径,可以使用:
PATH=$PATH:/the/path/to/add
程序员通常喜欢直接将单点符号加入PATH当中(代表当前目录)
PATH=$PATH:.
在shell脚本当中,变量可以通过双引号或者单引号包围。单引号会将所有字符视为普通字符,而双引号会保留特殊字符的含义。例如:
a=test
#输出$a
echo '$a'
#输出test
echo "$a"
如果你想在每个用户打开bash是都创建一个环境变量,那么可以将环境变量的声明放在/etc/profile.d
当中,创建一个以.sh
结尾的文件,将修改的环境变量放置在这个文件当中
当你每次打开一个交互式shell时,都会执行一遍$HOME/.bashrc
当中的命令。所以把环境变量的定义放在该文件当中,就可以持久化环境变量。同样,alias
命令效果也是不能持久的,可以通过将alias
的声明放置在$HOME/.bashrc
当中使其持久化
mytest=(one two three four five)
echo ${mytest[2]} #打印three
echo mytest #打印one。如果直接echo数组会打印第一个元素
#打印所有元素
echo ${mytest[*]}
#改变索引
mytest[2]=test
/etc/passwd
文件中储存了用户的相关信息
/etc/shadow
储存了用户的密码管理控制
/etc/group
保留了用户组信息
使用useradd
命令添加新用户
#查看创建用户时linux系统对于用户属性的默认值
useradd -D
#输出分析
#GROUP=100 新用户被添加到GID为100的公共组
#HOME=/home 新用户的目录将在/home下面
#INACTIVE=-1 新用户账户密码过期后不会被禁用
#EXPIRE= 新用户没有设置账户过期时间
#SHELL=/bin/bash 设置默认shell
#SKEL=/etc/skel 该目录文件会被复制到用户的HOME目录下
#CREATE_MAIL_SPOOL=no 在mail目录下是否创建用户接收邮件的文件
useradd -m test
#根据默认设置创建用户,默认不会创建用户HOME目录,通过添加-m选项使其创建HOME目录
# -c 给用户添加备注
# -d 指定主目录名称
# -e 指定账户过期日期
# -f 指定过期多少天被禁用
# -g 指定登录GID
# -G 指定除登录组外的一个或者多个附加组
# -k 和-m一同使用,复制/etc/skel到用户HOME目录
# -m 创建用户HOME目录
# -n 创建一个与用户同名的新组
# -r 创建系统用户(区别与个人用户)
# -p 指定默认密码
# -s 指定shell种类,默认bash
# -u 指定UID
userdel test
#删除账户同时删除用户HOME文件
userdel -r test
#修改账户信息
usermod [options] username
# -l 修改用户名
# -L 锁定账户,无法登录
# -U 接触锁定,使用户可以登录
# -p 修改账户密码
# -d 修改账户HOME目录
#修改用户密码
passwd username
#修改用户备注
chfn
#修改账户默认shell
chsh
#修改密码过期日期
chage
用户组操作不常使用,请大家自行查阅资料
例如:
-rw-rw-r-- 1 ted ted 1369 Mar 12 23:35 .emacs.bak
drwx------ 20 ted ted 4096 Aug 9 19:30 .emacs.d/
drwxrwxr-x 5 ted ted 4096 Apr 27 14:09 .eric6/
如图是三个文件在ls -l
命令的显示结果
文件权限描述字段一共由10个字符组成
-代表文件,d代表目录,l代表链接,c代表字符设备,b代表块设备,n代表网路设备
r:可读 w:可写 x:可执行
对象的属主,对象的属组,系统其他用户
umask
通过umask可以显示默认的文件权限掩码。rwx分别代表4 2 1,从文件夹默认的权限777或者文件默认权限666减去umask文件掩码就就是生成文件的权限
使用chmod改变文件权限,通过chown改变文件所属
#通过八进制代码改变文件权限
chmod 760 file
#通过+-改变文件权限
chmod u+x file
#意为给file的属主用户增加执行权限
chmod g-x file
#意为去掉属组的执行权限
#u 代表用户 g代表组 o代表其他 a代表上述所有
# r w x分别是读写执行
#改变文件的属主
chown owner file
有时间补充
有时间补充
创建bash时最好在第一行指定使用的shell,格式为
#!/bin/bash
然后就可以通过chmod u+x file.sh
赋予脚本运行权限,然后输入脚本文件的位置即可运行脚本
#显示this is a test
echo this is a test
#将echo输出与下一个命令显示在同一行
echo -n "the time and date are: "
date
#通过-n参数使得echo与date命令的输出显示在同一行
echo $HOME
#通过在环境变量前面加$使用环境变量,如果需要显示$,需要转义$为\$
test1=hello
test2=hey
echo "$test1 and $test2"
shell脚本可以从命令中提取信息,并且将信息赋给变量。然后就可以在脚本中随意使用变量了。
#反引号字符:
testing=`date`
#$()格式
testing=$(date)
echo "the date and a time are :" $testing
#使用示例:
today=$(date +%y%m%d)
ls /usr/bin -al > log.$today
command > output
将命令的输出重定向到output文件,并且覆盖原有的output文件
command >> output
将命令的输出重定向到output文件,并将输出追加到output文件后面
command < inputfile
将inputfile作为标准输入重定向到command命令
例如:
wc < test
#wc统计文本的行数,词数和字节数。这样命令就会输出test文件的总行数,单词数和字节数
#内联输入重定向
command << marker
data
marker
#内联输入重定向是输入多个数据,以marker(一个自己设定的标记开始和结束的字符)开头,以marker结尾
管道就是可以将一个命令的输出重定向到另一个命令的输入当中
command1 | command2
例如,寻找名为qq的进程
ps -A | grep qq
这样就可以通过管道,将ps命令的输出作为grep命令的输入,从而打印出名为qq的进程名称。
expr只支持整数运算,数字与符号之间有空格,需要考虑特殊字符的转义(例如*)
expr 1 + 5
#6
expr 5 \* 2
#需要对*号进行转义,输出10
示例:
var1=10
var2=20
var3=$(expr $var2 / $var1)
echo the result is $var3
方括号同样只支持整数运算,不需要考虑转义
var1=$[1 + 5]
var4=$[$var1 * ($var2 - $var3)]
echo $var4
使用内建的bc计算器
bc
就可以进入bc计算器
如何在脚本中使用bc
方法一:管道法
var1=$(echo "scale=4; 3.44 / 5" | bc)
echo $var1
通过scale指定小数点位数,也就是保留四位小数,最终输出是.6880
方法二:内联输入重定向
var=$(bc<
linux提供了$?
变量来记录上一个命令退出的状态码
echo $?
查看上一个命令退出状态码。如果是0就是正常退出
一般bash设置以一些内置linux退出状态码
状态码 | 描述 |
---|---|
0 | 命令成功结束 |
1 | 未知错误 |
3 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效退出参数 |
128+x | 与linux信号x相关的错误 |
130 | ctrl-c中断 |
255 | 正常范围外的状态码 |
同时命令可以通过exit num来指定退出状态码
最终状态码是num%255
基本结构:
if command
then
command
else
command
fi
条件判断:在bash中的if条件句并不是对bool类型的判断,而是对语句执行的退出状态码的判断,也就是如果command返回值是0(也就是执行成功),那么then后面的语句就会执行,否者不执行,执行else语句
if也可以有多条else语句
if也可以镶套执行
举例应用:
if grep ${testusername} /etc/passwd
then
echo found ${testusername}
else
echo ${testusername} is not exist
fi
如果用户 t e s t u s e r n a m e 存 在 , 就 显 示 第 一 个 e c h o , 否 则 显 示 第 二 个 e c h o ( 可 以 通 过 ‘ ‘ ‘ testusername存在,就显示第一个echo,否则显示第二个echo(可以通过``` testusername存在,就显示第一个echo,否则显示第二个echo(可以通过‘‘‘?```查看命令退出状态码)
上述的if条件句只能通过命令状态码判断运行。如果测试其他条件可以通过test
命令实现
命令格式:
test condition
当test条件成立是,返回状态码0,否则返回非0状态码
举例:
测试变量是否存在
A="";test $A;echo $?
输出1
A=0;test $A;echo $?
输出0
在bash中,提供了[ ]
可以像test
命令一样方便的测试条件
[]
之间与内部命令之间通过空格间隔开test
与[]
的数值比较功能表比较 | 描述 |
---|---|
n1 -eq n2 | == |
n1 -ge n2 | >= |
n1 -gt n2 | > |
n1 -le n2 | <= |
n1 -lt n2 | < |
n1 -ne n2 | != |
这些数值比较功能可以用在数值和变量上面
举例:
if [ $value -gt 5 ]
then
do something
fi
test
与[]
的字符串比较功能表比较 | 描述 |
---|---|
str1 = str2 | == |
str1 != str2 | != |
str1 \< str2 | <(记住转义小于符号,不然会被当成重定向) |
str1 \> str2 | >(记住转义大于符号,不然会被当成重定向) |
-n str1 | len(str1)!=0 |
-z str1 | len(str1)==0 |
在比较字符串时候,通常需要加额外字符保证不报错:
if test x$arg = xyes
then
...
fi
这是因为如果arg变量不存在,就会产生if test x = xyes
但是如果没有x,就会变成if test = yes
这样bash就会报错。所以添加x就是为了防止这种情况下的bash报错
test
与[]
的文件比较功能表比较 | 描述 |
---|---|
-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老 |
#两种形式
#AND
if [ condition1 ] && [ condition2 ]
#OR
if [ condition1 ] || [ condition2 ]
在bash中,双括号命令允许你使用高级数学表达式。
列出除了test命令中列举的其他数学操作
符号 | 描述 |
---|---|
! | 逻辑反 |
var++ | 后增 |
var– | 后减 |
++var | 前增 |
–var | 前减 |
~ | 位反 |
** | 幂运算 |
>> << | 右移与左移 |
& | | 布尔和与或 |
&& || | 逻辑和与或 |
举例:
var1=10
if (( var1 ** 2 > 90 ))
then
(( var1 = var1 * 10 ))
echo var1 is ${var1}
fi
双方括号提供了字符串高级比较特性
使用格式:
[[ expression ]]
其中expression的写法和test中定义的用法相同,但是额外提供了一个test命令当中没有提供的特性,那就是正则表达式
举例:
if [[ $USER == r* ]]
then
echo Hello
else
echo i do not know you
fi
在[[]]
当中就可以灵活的使用正则表达式进行各种字符串匹配操作
case使用效果可以达到多个if-else的效果
下面是case的基本使用模板:
case $var in
#通过 | 分割代表多种可能形式
thing1 | thing2 )
dosomething
;;
thing3 )
dosomething
;;
# * 代表默认所有可能
* )
default_something
;;
esac
形式:
for var in list
do
commands
done
举例:
#遍历自身定义的值(如果列表中出现特殊字符,如单引号,需要转义)
for test in A "C D" hh\'
do
echo i am $test
done
#输出:
i am A
i am C D
i am hh'
#从命令中取值(参考命令替换章节)
dir=.
for f in $(ls $dir)
do
echo "there is file $f"
done
#会枚举出当前文件夹所有文件
在bash当中,有一个叫做IFS(internal field separator)
的环境变量,中文名是内部字段分隔符。默认情况下,bash shell会通过空格,制表符,换行符
当做字段分隔符
如果想要更换字段分隔符,可以通过临时改变IFS环境变量来实现相应的效果
例如:
IFS=$'\n'
IFS=:
或者相应方式进行改变即可,改变过后可以通过IFS=
来重新定义回默认分隔符
我们可以通过使用bash命令当中加入双括号还引入c风格的for循环
例如:
for (( i=1,j=0; i<10; i++,j++ ))
do
echo i,j=$i,$j
done
在c风格for循环同样可以像上面一样使用多变量
while循环相对来说简单一些,格式如下:
while command
do
commands
done
其中command的用法和if中的用法完全一样,可以使用test
命令或者[ ]
while中可以含有多个条件命令,其中以最后一个命令的执行作为最终的结果
while command1
command2
do
commands
done
until命令和c语言当中的until命令基本相同,和while命令的用法很相似
until command
do
commands
done
command也可以像while命令一样加入很多的命令,并且以最后一个命令的退出码作为判断
break n
break
命令就像c语言当中的break命令一样,用来跳出循环。如果没有参数n,默认跳出当前循环,如果含有参数n,则表跳出多重循环,n==1表示跳出当前循环
continue n
continue命令和c语言中用法差不多,n的含义和上述break命令当中n含义相同
shell中你可以对循环的输出结果进行管道或者重定向
重定向:
for i in list
do
commands
done > test.txt
这样子循环所有的输出都被从定向到了test.txt当中
管道:
for i in list
do
commands
done | sort
这样子命令的最总结果会输出排序后的输出
命令行参数通过下面模式输入:
./some.sh arg1 arg2
$0
代表脚本的名称,$1
是第一个参数,以此类推,$9
是第九个参数
示例:
echo the file name is $0
echo the first arg is $1
可以通过if [ -n "$1" ]
来检查是否含有参数
对于bash shell脚本$#
会包含总参数个数的信息,值就是一共多少个参数
如果你想表示最后一个参数是什么,应该这么做:
echo the last arg is ${!#}
注:为什么使用感叹号而不是${$#}
,因为花括号里面当然不能使用$
,可以在花括号里面使用!
来达到代替$
的目的
我们可以通过$*
和$@
来抓取所有的参数
$*
会把所有的命令行参数当成一个字符串,所以不能通过for遍历
$@
会把命令分割成多个独立的单词,所以可以通过for循环遍历
shift
命令命令使用方法:
shift
使用shift
命令,默认情况下他会把每个参数都同时向左移动一个位置,这样子,$3
的值就变成了$4
的值,以此类推。$1
的值就会被删除,但是$0
的值并不会改变
我们可以通过shift 9
来读取第10个和之后的参数,这也是脚本程序的通常做法
Linux中的标准文件描述符号
文件描述符 | 缩写 | 描述 |
---|---|---|
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误 |
使用输入重定向时,linux会用指定的文件去替换标准输入文件描述符
使用输出重定向可以用来改变输出重定向的指向
默认情况下,标准错误输出也是和标准输出一样,输出到屏幕上面,但是STDERR不会随着STDOUT的重定向而改变。
command 2> file
我们可以通过用数字指明你要重定向的文件描述符,这样子就可以重定向错误输出了
如果想要把输出和错误重定向到不同的文件当中,可以使用这种方法
command 2> err_file 1> out_file
如果想要把输出和错误重定向到相同的文件当中,可以使用&>
符号
command &> file
如果通过&>
重定向错误和输出,因为错误在bash当中具有较高的优先级,所以输出的顺序可能会和你的预想不太相同,错误会集中的显示在文件的开始部位,然后才是输出的信息的显示
临时重定向
我们在脚本当中,可以把单独的一行重定向输出到一个选定的文件描述符当中,例如:
echo "this is an error" >&2
这样子echo输出的数据就会当成标准错误显示出来
永久重定向
重定向每个echo语句就会十分的繁琐,我们可以通过exec语句实现重定向某个特定的文件描述符
exec 1>file
在此执行的脚本的标准输出就会被重定向到file当中
exec 2>another_file
在次执行的语句的标准错误都会被重定向到another_file当中
重定向输入
exec 0 < file
这样就会从file当中获取STDIN的数据了
创建输出重定向
可以通过exec命令文件描述符分配重定向的文件。例如如果文件描述符3可用。那么
exec 3>testout
这样就创建了一个输出的重定向,我们可以通过
echo "some thing" >&3
把命令的输出对应到某个文件描述符当中。当然命令还有很多的灵活操作,下面我们一个一个介绍
重定向输出文件描述符
如果你可以分配一个文件描述符给标准文件描述符,同样,你也可以将标准文件描述符分配给其他人,见下面的例子:
把文件描述符3的输出定向到1的输出
exec 3>&1
把文件描述符1的输出定向到test的输出
exec 1>test
此时 3 = 屏幕输出;1=test
下面的语句将会输出到test文件句柄的写入
echo "where am i"
把1的输出重新定向到屏幕输出当中
exec 1>&3
下面语句重新显示在屏幕上面
echo "i am on the screen "
创建输入文件描述符与重定向输入文件描述符
类似上面的操作,我们同样可以重定向输入文件描述符
把文件描述符6的输入定向到0的输入
exec 6<&0
把文件描述符0的写入定向到test的读取
exec 0
创建读写文件描述句柄
举例:
exec 3<>test
read line <&3
echo "read line &line"
echo "write line " >&3
关闭文件描述符
shell结束会,会自动关闭所有的文件描述符,但是有时候你也同样需要手动关闭文件描述符
exec 3>&-
通过这样,就可以关闭句柄为3的文件描述符
lsof
我们可以通过lsof命令列出所有打开的文件描述符
这个命令会产生大量的输出,会列出当前linux上所有打开的文件的文件的相关信息。
这里我只讲解一下最常用的两个选项,-p
和-d
选项
lsof -a -p $$ -d 0,1,2
这条语句调用了lsof打印当前进程0,1,2三个句柄的信息。其中-p后面跟进程号,$$
在shell当中代表当前进程号。-d表示选择当前要打印的进程文件描述符。-a代表将符合-p选项和符合-d选项的所有文件描述符取交集
lsof的默认输出
列 | 描述 |
---|---|
COMMAND | 正在运行的命令的前九个字符 |
PID | 进程ID |
USER | 进程属主 |
FD | 文件描述符号和访问类型 |
TYPE | 文件的类型 CHR:字符型 BLK:块型 DIR:目录 REG:常规文件 |
DEVICE | 设备的设备号 |
SIZE | 表示文件的大小 |
NODE | 本地文件的节点号 |
NAME | 文件名 |
有时候你可能不想显示脚本的输出,比如说你想将脚本当做后台程序运行,这时候你应该阻止文件的输出
linux下有一个特殊文件,就是/dev/null
,null文件和他的文件名一样,文件里面什么都没有,输出到null文件里面到任何数据都不会保存,全部都丢掉了。
举例:
丢弃command的标准输出
command > /dev/null
丢弃command的错误
command 2> /dev/null
清空某文件
cat /dev/null > file
linux 使用/tmp
目录存放不需要永久保存的文件,大多数linux发行版配置了系统启动时自动删除/tmp
目录下的文件
系统任何账户都有权限创建临时文件,不用担心清理工作。
创建本地临时文件
默认情况下,mktemp会在本地目录创建一个文件,用mktemp命令创建一个临时文件只需指定一个文件名摸板就行了。模板可以指定任意文本文件名,在文件末尾添加六个X就行了(一般是六个,其实任意就行)
tmpfilename=$(mktemp test.XXXXXX)
命令返回创建的文件的文件名称,保证文件的唯一性,不重名。
在/tmp
目录下创建临时文件
-t
选项会强制mktemp命令在系统临时目录下创建临时文件,这样mktemp命令返回的是文件的全路径名称而不是只有文件名。
创建临时目录
-d
选项会使得mktemp命令创建临时目录而不是临时文件,这样你可以针对目录进行一些任何操作。
将输出同时发送到显示器和日志文件,这种做法有时候可以排上用场。
tee命令相当于管道的一个T型接头,将从STDIN过来的数据同时发送给STDOUT和tee命令指向的文件
date | tee filename
如果是追加到文件的尾部
date | tee -a filename
信号 | 值 | 描述 |
---|---|---|
SIGHUP | 1 | 挂起进程 |
SIGINT | 2 | 终止进程 |
SIGQUIT | 3 | 停止进程 |
SIGKILL | 9 | 无条件终止进程 |
SIGTERM | 15 | 尽可能的终止进程 |
SIGSTOP | 17 | 无条件停止进程,但是不是终止 |
SIGTSTP | 18 | 停止或者暂停进程,但不终止进程 |
SIGCONT | 19 | 继续停止运行的进程 |
默认情况下,bash shell会忽略收到的SIGQUIT和SIGTERM信号,但是会处理SIGHUP和SIGINT信号。
SIGINT
通过输入ctrl c组合键发送SIGINT信号停止shell当前运行的进程。
SIGTSTP
通过输入ctrl z组合键生成一个SIGTSTP信号,停止shell中运行的任何进程,但是停止的进程会继续保留在内存当中,可以通过fg命令恢复暂停的进程。可以通过ps -l
查看当前暂停的作业
SIGKILL
可以通过kill -9 PID
来给对应的进程发送SIGKILL信号杀死进程。
也可以不忽略信号,在信号出现时候及时的捕获他并执行其他命令。trap命令可以允许你指定shell脚本要监控那些shell中拦截的信号。
捕获信号
trap commands signals
trap "echo 'i have trapped ctrl c'" SIGINT
捕获脚本退出
trap commands EXIT
在脚本执行结束之后就可以执行对应commands
移除捕获
trap -- signals
在命令后面添加一个&
就可以放进后台运行。
command &
然后返回作业号(在中括号当中)
在终端程序当中使用后台进程一定要小心,在终端中运行后台进程时候,每一个后台进程都对应一个终端会话(pts),如果终端会话退出,后台进程也会退出。
如果想要在终端退出也继续运行后台进程,可以通过使用nohup命令来拦截所有的SIGHUP信号,同时切断进程和终端的联系,所有的输出都会追加到一个nohup.out文件当中。
nohup ./comm.sh &
jobs
jobs命令可以查看分配给shell的作业,想要查看作业的PID,只需要添加一个-l
选项
参数 | 描述 |
---|---|
-l | 列出PID和作业号 |
-n | 列出上次shell发出通知后改变状态的作业 |
-p | 只列出PID |
-r | 只列出运行的作业 |
-s | 只列出停止的作业 |
在jobs中带有+号的作业是默认作业,如果其他作业操作不指定作业号,则默认针对该作业。带有-号的作业是下一个成为默认作业的作业
通过bg
命令加作业号可以重启停止的作业放在后台,如果没有作业号,就是默认的作业
如果想要在前台重启作业号,可以使用fg
命令加作业号,否则是默认作业
调度优先级就是内核分配给进程的CPU时间。在linux当中,有shell启动的所有进程的调度优先级都是相同的。调度优先级是整数,从-20(最高优先级)到19(最低优先级)。
nice -n 10 command
调整谦让度为10
如果提高优先级需要有root权限
renice -n -20 -p PID
同样renice命令不能提高优先级,除非有root权限
使用at命令
at会将作业提交到队列当中,指定shell何时运行该作业。atd是at的守护进程。atd守护进程会检查/var/spool/at
没记录来获取at命令提交的作业。默认情况下会60s检测一次,如果时间匹配,就会运行作业。
at [-f filename] time
时间格式有很多类型,可以参考百度
at的输出默认发送到用户的邮箱当中,如果添加-M
选项就可以忽略命令的输出。所以建议在脚本当中使用重定向。
可以使用atq
命令查看作业信息,atm 作业号
删除指定的作业
使用cron时间表
对于需要定期执行的命令,可以使用cron时间表
cron时间表采用如下格式min hour dayofmonth month dayofweek command
指定作业何时执行。
可以使用通配符。如果想在每周一10:15执行:15 10 * * 1 command
。
可以通过crontab -l
命令查看已有的时间表,使用crontab -e
更改时间表
如果你的脚本对时间精确性要求不高,可以通过在/etc/cron\.*ly
目录中添加脚本达到定期执行脚本的作用。例如每天daily,每周weekly
如果对应作业时间时系统处于关机状态,想要此时执行一遍没有执行的作业,可以使用anacron程序,这个详见百度。
基本的脚本函数
有两种创建函数的方式:
function name{
commands
}
name(){
commands
}
使用函数
函数的定义一定要放在函数使用的前面
function fun1{
echo "i am a function"
}
fun1
返回值
如果不指定返回值,默认返回的是最后一个命令执行的状态码,这样子非常危险
可以通过return返回函数的返回值
db1{
read -p "input a value " value
return [ $value * 2 ]
}
但是返回值必须是0-255之间的值,而且需要立马获取$?
,不然就会被覆盖
有另一个方法接受函数返回值。那就是通过echo返回给获取的变量
db2{
read -p "input a value " value
echo $[ value * 2 ]
}
result=$(db2)
向函数传递参数
在脚本中向函数传递参数时候可以讲函数和参数写在一行,然后在函数中可以向脚本中一样通过$0-9
获取函数参数
函数中的全局变量和局部变量
在函数当中,如果直接使用变量,默认是使用全局的变量。所以是十分危险的。我们一般指定局部变量,通过local关键字
db(){
local i = 0
}
i=1
db
echo $i
这样就不会改变全局变量的值
通过函数返回数组
function arraybdlr{
local origarray
local newarray
local elements
local i
origarray=($(echo "$@"))
newarray=($(echo "$@))
elements=$#
for (( i = 0; i < elements; i++)){
newarray[$i]=$[ ${origarray[$i]} * 2 ]
}
echo ${newarray[*]}
}
myarray=(1 2 3 4 5)
echo "the origin array is ${myarray[*]}"
arg1=$(echo ${myarray[*]})
result=($(arraybdlr $arg1))
echo "the new array is ${result[*]}"
bash shell函数支持递归
对于bash来说,如果使用bash运行一个脚本,相当于新建一个bash进程来运行脚本,所以在bash中作用域只是脚本内部。可以通过source命令达到使用当前bash执行命令的效果
source test.sh
. test.sh
这样子就可以调用脚本中定义的函数了
定义函数的最佳场所是.bashrc
,因为这个文件每次启动一个新shell都会运行
sed编辑器可以根据命令来处理数据流当中的数据。这些命令要要么储存在一个命令行文本文件当中,要么从命令行输入。
sed编辑器会执行一下操作
在流编辑器处理玩一行数据之后,他会读取下一行数据然后重复这个过程,直到处理完流当中的所有数据。
sed命令格式:
sed options script file
sed命令选项:
选项 | 描述 |
---|---|
-e script | 在处理输入时候,把script命令(script指单条sed命令)添加到已有命令当中(这个选项主要是用来指定多条处理指令,原本默认只有一条处理指令) |
-f file | 在处理输入时候,把file文件当中的命令添加到已有的命令当中 |
-n | 不产生命令输出,使用print命令完成命令输出(通常和p选项一同使用) |
例如:
#替换test为big test
echo "this is a test" | sed 's/test/big test/'
#同上
sed 's/test/big test/' data1.txt
sed -e 's/brown/green'; s/dog/cat/' data1.txt
sed -e '
> s/brown/green/
> s/fox/dog/' data1.txt
sed -f script1.sed data1.txt
当文件当中读取命令时候,不需要使用分号隔开,因为默认每行都是一条单独的命令
替换标记
替换简单实例:
sed 's/test/trial/' data.txt
这样子设置之后data.txt中每一行的第一处test都会被转换成trial(默认情况下只替换每行出现的第一处)。可以使用替换标记替换一行当中不同位置的文本。
s/pattern/replacement/flags
flags有四种可以标记的形式:
flag | 说明 |
---|---|
数字 | 表明新文本将会替换第几处模式匹配的地方 |
g | 表明新文本将会替换所有匹配的文本 |
p | 表明将原先行的内容print出来(通常和-n选项一同使用) |
w file | 讲替换的结果写到文件当中(只写替换的行,没有进行替换的行不写入) |
如果想要只输出替换过的行,可以通过p选项和-n结合使用。
sed -n 's/test/trial/p' data.txt
这里指定-n选项表示不把流处理的文本全部输出,只输出print的内容。然后p选项指定print所有替换和更改的行。这样子就可以只输出更改的行(更w选项效果类似)
替换标记
如果在替换的过程中,发现了/
字符,这种情况下,有两种解决方案:
/
前面加上\
变成\/
sed 's!/bin/bash!/bin/csh!' /etc/passwd
默认情况下,sed使用的命令作用于文本数据的所有行。如果只想控制命令作用与某些特定的行,必须使用行寻址。
sed编辑器有两种形式的寻址:
两种形式都通过相同格式指定地址:
[adderss]command
也可以将特定地址的多个命令分组:
address {
command1
command2
command3
}
sed编辑器会将命令作用到相应的行上面
数字方式寻址
命令中可以指定单个行号或者使用起始行号,逗号和结尾行号进行标注
sed '2s/dog/cat/' data.txt
sed '2,3s/dog/cat/' data.txt
如果想要将命令从第某一行可以使用美元符号。美元符号在sed中可以表示最后一行
sed '2,$s/dog/cat/' data.txt
使用文本模式过滤
sed允许指定文本模式来过滤出来命令需要的行,格式如下:
/pattern/command
必须使用正斜线把pattern封装起来。例如:
sed '/ted/s/bash/csh/' /etc/passwd
这里指定的ted跟cat /etc/passwd | grep ted
效果相同,表示包含可以正则匹配到的行。
命令组合
如果在单行指定多条命令,可以通过花括号隔开
sed '2{
> s/fox/ele/
> s/dog/cat/
> }' data.txt
sed '2{s/fox/ele;s/dog/cat}' data.txt
删除命令d会删除所有命令当中匹配的行位置。使用命令时候应该格外小心,因为如果你忘记加入寻址模式时候,流当中的所有文本行都会被删除。例如:
sed '3d' data.txt
删除第三行。
sed '2,3d' data.txt
删除特定的区间
sed '/ted/d' /etc/passwd
删除模式匹配的行位置。(同上文当中的模式匹配方式)
同样也可以使用两个文本模式来指定某个删除的区间。例如一个文本test.txt如下:
test.txt
a
b
c
a
d
e
这种情况下如果使用sed '/b/,/a/d' test.txt
,就会删除b-a的所有内容,然后输出:
a
e
但是如果是sed '/a/,/c/d' test.txt
,结果就是输出一个空文件。因为匹配到第一个模式时候打开删除功能,匹配到c时候关闭删除功能。但是第二次匹配到a时候再次打开删除功能。然后一直删除到结尾。同样,如果对文本使用sed '/b/,/w/d' test.txt
因为没有找到w字母,所以sed就直接删到结束位置,故最终输出只有a
。
sed允许用户插入和添加文本。
格式如下:
sed '[address]command\newline'
注意的是command后面是反斜杠,这一点和前面的几个不一样。
使用实例:
#第三行前面计入new line
sed '3i\new line' data.txt
#最后一行前面加入endline
sed '$i\endline' data.txt
使用c命令可以修改某一个特定的行。格式跟前者相同。
使用实例:
sed '/ted/c\this is the change of ted line' data.txt
sed '2,3c\this is the change line'
转换命令是唯一一个可以处理单个字符的sed命令。格式如下:
[address]y/inchars/outchars/
转换命令会把inchars中的第一个字符变成outchars中的第一个字符,第二个字符转换成outchars中的第二个字符。这个映射过程一直持续到所有字符转换完成为止。如果inchars和outchars长度不想听,编辑器会进行一次报错。
例如:
echo "this 1 is 1" | sed 'y/123/456/'
输出为this 4 is 4
。
他会转换匹配的每一行,无法指定行中特定的位置
之前简要介绍了如何在sed当中使用p和-n选项结合打印需要的信息。这里面介绍一共三个打印命令。
=
打印行号打印行
echo "this is test" | sed 'p'
输出:
this is test
this is test
常用做法是打印模式匹配的行:
sed -n '/test/p' data.txt
这样就会打印包含test的行(效果和grep test data.txt
相同。
如果你想在修改之前先显示当前行的内容,可以通过如下命令:
echo -e "a\nb\nc" | sed -n '/b/{p;s/b/hhh/p}'
这样输出:
b
hhh
打印行号
使用=
。
例如如果想要打印某一行的行号和内容:
sed -n '/test/{=;p}' data.txt
列出行
列出命令(list)可以用来打印数据流当中的不可打印的ASCII字符。任何的不可打印的字符要不然在其八进制值前面加上一个反斜杠,或者使用C风格的命令法(例如\t
)。这个命令常用来打印不可打印的文件字符。
用法:
sed -n 'l' data.txt
写入文件
[address]w filename
例如下面将文件的前两行写入test.txt当中
sed '1,2w test.txt' data.txt
当然如果你同时不想让它显示原本的文件内容可以指定-n选项。
从文件读取
read命令可以是你将一个独立的文件的数据插入到数据流当中。sed会将文件插入到指定的地址后面。
格式是:
[address]r filename
sed '3r data12.txt' data.txt
sed '$r data12.txt' data.txt
这样data12.txt就被插入到data.txt第三行后面了。
读取命令一个很常用的用法就是占位符文本,例如一个文本内容是
would the following people
LIST
please report to the ship's caption
如果使用名单列表替换LIST内容:
sed '/LIST/{r namelist.txt;d}' data.txt
sed编辑器命令通常针对的都是单行命令。但是其实sed当中包含了三个用来处理多行文本的特殊命令。
单行版本的next命令
小写的n命令会告诉sed编辑器移动到数据流下一行文本当中,而不用重新回到命令最开始执行一遍。通常sed会移动到下一个数据流之前会在当前行执行完所有定义的命令。
如果你想删掉包含header单词所在行的下一行,你可以使用命令:
sed '/header/{ n ; d }' data.txt
这样找到匹配行之后就会执行n变到下一行执行d
合并文本行
单行版本n命令会将当前数据流的下一行移至sed工作空间(或者说是模式空间)。多行版本的next命令(大写N)回见下一行文本添加到模式空间已有的文本后面。
文本行仍使用换行符分割。
例如如果想要合并包含first的行和下一行,可以使用:
sed '/first/{ N ; s/\n/ / }' data.txt
但是在使用N命令时要考虑一个特殊情况,那就是如果sed在执行最后一行时候,如果出现了N命令,sed编辑器就会停止执行(不执行N后面的命令了)。所以编写脚本同时也要注意解决这个问题。
大写的D命令和d命令不相同。在使用N命令时候,一次加载了两行。这个时候如果使用d命令,就会同时删除两行。但是这个未必是期望的结果。
如果使用D命令,sed只会删除到第一个换行符为止(也就是删除最开始的第一行)
例如删除数据流当中出现在第一行前的空白行:
sed '/^$/{ N ; /header/D}' data.txt
多行打印命令大写P和多行删除命令类似,只打印多行模式空间当中的第一行。包括模式空间当中的直到换行符的所有字符。这个选项也同样常和-n选项一同使用。
模式空间(pattern space)是一个活跃的缓冲区,sed执行命令时会保存待检查的文本。保持空间(hold space)是用来执行某些行时可以用来保存一些行。有以下几条命令操作保持空间:
命令 | 描述 |
---|---|
h | 将模式空间复制到保持空间 |
H | 将模式空间附加到保持空间 |
g | 将保持空间复制到模式空间 |
G | 将保持空间附加到模式空间 |
x | 交换模式空间和保持空间的内容 |
例如如果你想要交换所有的包含first的行和下面一行(也就是反序输出这两行)可以使用下面命令:
sed -n '/first/ {h ; n ; p ; g ; p }' data.txt
在sed命令使用当中,你可以配置命令使其不要作用到数据流当中的特定位置上面。感叹号命令可以用来排除命令。也就是原本起作用的命令不起作用。
sed -n '/header/!p' data.txt
这样子命令只作用到模式没有匹配的数据流上面。也就是包含header的行不会打印出来。
如果有了排除命令,如何实现将全文行反转的效果呢?下面我们介绍一下全文行反转的思路:
所以可以使用下面命令
sed -n '{1!G ; h ; $p}' data.txt
sed编辑器提供了可以基于地址,地址模式或者地址区间排除一整块命令。这允许你在特定行执行一组命令
分支命令b格式如下:
[address]b [label]
address决定了哪些行会触发分支命令。label参数决定了要跳转的位置。如果没有label参数,跳转命令会跳转到脚本结尾
例如如果想要在数据流的2,3行跳过替换命令,可以使用以下脚本:
sed '{2,3b ; s/test/hhh/ }' test.dat
如果不想直接跳转到行尾,可以指定一个跳转的标签,标签是以冒号开始的,最多7个字符长度
sed '{/first/b jump1 ; s/a/b/ ; :jump1 ; s/c/d/ }' data.txt
这样就可以指定跳转位置了。如果跳转位置在跳转语句前面的话,就可以实现循环的功能
测试命令t也可以用来改变sed脚本执行流程。测试命令会根据替换命令结果跳转到某个标签,而不是根据地址进行跳转。
如果替换命令成功匹配并且替换了一个模式,测试命令就不会执行跳转,否则跳转。
测试命令格式:
[address]t [label]
例如:
sed '{s/a/b/ ; t ;s/b/c/}' data.txt
如果第一个替换成功,就会跳过后面的命令。
在sed执行替换命令时候,如果你想通过模式方法一次匹配替换多个模式对象,使用这种方法是行不通的。例如你想在所有的cat和hat外层添加一个括号:
echo "the cat is in a hat" | sed 's/.at/".at"/g'
这样子最终的输出结果是the ".at" is in a ".at"
。为了解决这个问题,sed提出了模式替换。
&符号可以表示替换命令当中匹配的模式。不管模式匹配了什么文本,都可以在替代模式当中使用&来表示这段文本。
所以可以这样实现上述的目的:
echo "the cat is in a hat" | sed 's/.at/"&"/g'
这样子模式替换的最终输出就是the "cat" is in a "hat"
&符号会提取匹配替换命令中指定的模式的整个字符串。有时候你只是想提取这个字符串的一部分。
sed当中使用转移的圆括号表示每个子模式(这一点和平常转义的用法正好相反,因为必须使用转义符号表示分组字符。),同时在替换一方使用转义符号加数字表示第几个模式。例如用法如下:
echo "that furry cat is pretty" | sed 's/furry \(.at\)/\1/'
这样最终输出就是that furry "cat" is pretty
相对于sed而言,gawk是一套更加高级的文本流处理工具,他可以提供一个类变成的环境来修改和重新组织文件当中的数据。
gawk编程语言当中,你可以:
gawk通常用来处理大日志文件,其格式如下:
gawk options program file
gawk通常使用选项:
选项 | 描述 |
---|---|
-F fs | 指定行中划分数据字段的分隔符 |
-f file | 从指定文件当中读取程序 |
-v var=value | 定义gawk当中一个变量和他的默认值 |
-mf N | 指定要处理数据当中最大字段数 |
-mr N | 指定数据文件当中的最大数据行数 |
-W keyword | 指定gawk兼容模式和警告等级 |
将脚本放置在两个花括号当中。
gawk '{print "Hello world!"}' data.txt
gawk假定脚本是单个字符串,所以要放进单括号当中。
gawk和sed命令类似,也是对每一行进行处理。所以gawk程序会针对数据流每行执行脚本。
gawk自动给一行每个数据元素分配一个变量。默认情况下,gawk会将如下变量分配给数据字段
数据字段通过分隔符划分。默认分隔符是空白字符(例如空格和制表符)
gawk '{print $1}' data.txt
打印文本数据第一列
当然你可以指定其他分隔符,例如:
gawk -F: '{print $1}' /etc/passwd
打印出来密码文件的第一个数据字段
gawk程序允许通过分号分割语句,例如
echo "i am rich" | gawk '{$4="Christ";print $0}'
输出:i am Christ
下面有命令文件
scr.gawk
{print $1 "is" $6}
然后执行
gawk -f scr.gawk test.txt
这样就可以实现文件中储存命令。如果有多条命令,可以使用分好隔开,也可以使用每行一条命令(不需要分号)。
也许你想在开始逐行处理前运行一些处理脚本,或者逐行处理后运行一些脚本,可以使用BEGIN和END关键词:
gawk 'BEGIN {print "begin the content"} ; {print $0} ; END {print "end of the content"}' test.txt
这样子就会输出内容当中,开始是begin the content,结尾时end of the content。达到先后处理的效果
字段和记录分隔符变量
数据字段可以让你使用$
+数字的形式使用第n个字段。数据字段是通过字段分隔符划定的。默认情况下,字段分隔符是一个空白字符(也就是空格或者制表符)。之前说过可以通过-F选项指定使用的字段分隔符。也可以使用FS内建变量来分割字符。gawk有以下内建变量:
变量 | 描述 |
---|---|
FLELDWIDTHS | 通过空格分割的一列数字,定义了数据字段确切宽度 |
FS | 输入字段分隔符 |
RS | 输入记录分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
如果数据文件data.txt
内容如下
data1,data2
data3,data4
如果数据是逗号分割,你可以通过这样:
gawk 'BEGIN{FS=","} {print $1,$2}' data.txt
打印读取打印data.txt内容。如果你想让输出数据通过横线分隔开,可以使用:
gawk 'BEGIN{FS=",";OFS="-"} {print $1,$2}' data.txt
如果你的数字文件是通过定长的格式存放的,例如data.txt
:123.4-5.67
你可以使用gawk 'BEGIN{FIELEWIDTHS="4 4"}{print $1,$2}' data.txt
这样每一个字段就会按照指定的长度读取,打印123.4 -5.67
因为默认每一行都是一条gawk的记录数据流,所以RS和ORS默认都是换行符。如果你的记录是多行。常用的处理方法是多行记录之间通过一个空行分割开来。然后设置FS是换行符,RS是空白字符,就可以了
#cat文件内容如下:
#1\n2\n\n3\n4\n\n5\n6\n
gawk 'BEGIN{FS="\n";RS=""} {print $1,$2}' data.txt
#输出是:
#1 2\n3 4\n5 6
现在每一行都是一个字段,空白行是数据分隔符
数据变量
变量 | 描述 |
---|---|
ARGC | 命令行参数个数 |
ARGIND | 当前文件在ARGV中的位置 |
ARGV | 包含命令行参数的数组 |
CONVFMT | 数字转换格式,默认是%.6 g |
ENVIRON | 当前shell环境变量及其值组成的关联数组 |
ERRNO | 读取或者关闭输入文件时发生的系统错误号 |
FILENAME | gawk输入数据的数据文件名 |
FNR | 当前数据文件数据行数 |
IGNORECASE | 如果这个值不是0,忽略字符串大小写 |
NF | 数据文件当中字段总数 |
NR | 已处理的数据记录个数 |
OFMT | 数字输出格式,默认是%.6g (也就是小数点后面最多6位) |
RLENGTH | 由match函数匹配的字符串长度 |
RSTART | 由match函数匹配的子字符串起始位置 |
使用ARGC和ARGV变量时候,ARGC表示参数个数(这个不是c语言里面的ARGV,这个是gawk命令后(也就是引号之后)的命令参数个数,例如:
gawk -F: 'BEGIN{print ARGC,ARGV[0],ARGV[1],ARGV[2]} data1.txt data2.txt
此时输出应该是3 gawk data1.txt data2.txt
。这里0号参数表示的是命令名称,接下来参数表示的是gawk命令之后的参数(这不包含gawk参数和gawk命令)。同时,脚本中引用脚本变量不需要美元符。
ENVIRON变量是一个关联数组。关联数组使用文本作为数组引索。例如打印HOME环境变量和PATH环境变量:
gawk '{BEGIN{print ENVIRON["HOME] ; print ENVIRON["PATH]}}' test.txt
如果你想打印第一列数据和最后一列数据,可以通过这样:
gawk 'BEGIN{FS=":";OFS=":"} {print $1,$NF}' /etc/passwd
这里因为NF表示的是字段总数,所以$NF就是最后一个字段了
这里详解一个FNR和NR变量含义的不同:NR表示的是一共处理了几个记录(默认是一行是一个记录,可以通过RS指定记录分隔符)。即使你处理了多个文件的记录,这个NR的值也是累加的。但是FNR表示的是单个文件当中处理的记录个数。也就是每次处理其他文件时候FNR的值都会从1开始。
在脚本中给变量赋值
gawk 'BEGIN{test="it is a test";print test}'
这样就会输出it is a test
。同样变量可以进行数学运算:
gawk 'BEGIN{x=2;x=(x+3)*2;print x}'
这样就会输出10。gawk支持标准算术操作符,包括求余(%)和幂运算(^或者**)
在命令行定义变量
gawk可以通过使用-v选项在命令行上面指定程序需要用到的变量:
gawk -v x=2 'BEGIN{x=x**2;print x}'
最终输出是4
可以使用标准赋值语句来定义数组变量:
var[index] = element
其中index引索值可以是数字也可以是字符串:
gawk 'BEGIN{test["test"]="hhh";print test["test"]}'
gawk 'BEGIN{var[1]=25;var[2]=31;tot=var[1]+var[2];print tot}'
我们可以通过for循环遍历数组
for (var in array)
{
statements
}
for循环会循环数组的引索值然后执行statement。你可以通过引索值取出数组内容。
例如:
gawk 'BEGIN{var["a"]="x";var["b"]="y";for (test in var){print "Index:",test," - Value:",var[test]}}'
这样最终是输出就是:
Index: a - Value: x
Index: b - Value: y
以下命令从关联数组中删除一个元素:
delete array[index]
在gawk当中,你可以使用BRE和ERE正则表达式。使用正则表达式时候,正则表达式必须是出现在花括号前面
gawk 'BEGIN{FS=","} /exp/{print $1}' data.txt
这样匹配了所有符合exp的记录,其中exp甚至可以包含字段分隔符
匹配操作符允许把正则表达式作用做特定的字段上面,匹配操作符就是波浪线,例如:
$1 ~ /^data/
使用示例:
gawk '$2 ~ /exp/{print $0}' data.txt
这样他就会输出所有的第二个字段包含exp表达式的所有记录
一个更加实用的示例:
gawk -F: '$1 ~ /rich/{print $1 ,$NF}' /etc/passwd
这样就会输出rich所用的shell名称
如果你想使用排除,只需要在波浪号前面添加一个感叹号见就行了
gawk -F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd
这样就会输出所有不匹配的记录
除了正则表达式之外,你也可以使用数学表达式。例如你如果想要显示所有属于root用户组的用户,你可以使用:
gawk -F: '$4 == 0 {print $1}' /etc/passwd
这样就会输出所有第四个字段是0的用户
可以使用的数学表达式:
x == y x != y
x <= y x >= y
x > y x < y
跟正则表达式不相同的是,数学表达式必须完全匹配,而不是仅仅的正则匹配
if语句格式可以是:
if (condition)
statement
if (condition) statement
如果statement需要时多条语句,就是用花括号括起来
当然if也支持else子句
例如:
gawk '{if ($1 > 20) print $1 * 2;else print $1 /2}' data.txt
while语句格式:
while (condition){
}
例如data5内容是:
130 120 135
160 113 140
145 170 215
执行
gawk '{total = 0;i = 1;while (i < 4){total += $i;i++;};avg = total / 3;print "Average:",avg;}' data5
这样就会输出每一行的平均值
当然gawk的循环当中也支持break和continue语句
用法和while类似,格式:
do
{
statement
} while (condition)
格式:
for( variable assignment; condition; iteration process)
改写之前while循环的功能:
gawk '{
total = 0
for (i =1 ; i <4; i++)
{
total +=$i
}
avg = total / 3
print "Average:" ,avg
}' data5
gawk除了print之外还有printf命令提供类似C语言的格式化打印功能
printf "format string",var1,var2,...
格式化占位符采用这种格式:
%[modifier]control-letter
其中control-letter是单字符指定显示什么类型数据,可以是:
控制字母 | 描述 |
---|---|
c | 将一个数作为ACSII显示 |
d | 显示一个整数值 |
i | 同d |
e | 科学计数法显示一个数 |
f | 显示一个浮点数 |
g | 用科学计数法或者浮点数显示(选择其中较短的一个) |
o | 显示一个八进制数 |
s | 显示一个文本字符串 |
x | 显示一个16进制数字 |
X | 显示一个16进制数字,但是使用大写字母 |
除了控制字母之外,还有三种修饰符可以进一步控制输出
例如如果想要输出左对齐的浮点数,最短字符个数是10(空格补齐),小数点后5位:%-10.5f
如果是字符串,最短15字符,左对齐:%-10s
函数 | 描述 |
---|---|
atan2(x,y) | x/y反正切,x,y单位是弧度 |
cos(x) | x为弧度单位的余弦 |
sin(x) | 同上 |
exp(x) | x指数函数 |
int(x) | x取整,取靠近0一侧 |
log(x) | x自然对数 |
rand() | 0-1随机浮点数 |
sqrt(x) | x平方根 |
srand(x) | 为随机数指定种子 |
除了标准数学函数之外,还提供了位操作函数 :
函数 | 描述 |
---|---|
and(v1,v2) | 按位与 |
compl(val) | val补运算 |
lshift(val,count) | 左移 |
rshift(val,count) | 右移 |
or(v1,v2) | 按位或 |
xor(v1,v2) | 按位异或 |
函数 | 描述 |
---|---|
asort(s [,d]) |
根据数组s元素值排序。引索值会被替换成为新的排序顺序的连续数字。如果指定了d,排序后结果储存在d当中 |
asorti(s [,d]) |
将数组按引索值排序。生成数组将引索值作为数据元素,按照连续数字引索表示顺序。如果指定了d,结构储存在d当中 |
gensub(r,s,h,[,t]) |
查找变量$0或者参数t来匹配正则表达式r。如果h是g或者G,就用s替换掉匹配的文本。如果h是一个数字,替换掉第h个匹配r的地方 |
gsub(r,s [,t]) |
相当于上式h=g |
index(s,t) |
返回字符串t在s当中的引索值 |
length([s]) |
返回s长度,如果没有s,返回$0长度 |
match(s,r [,a]) |
返回s中正则表达式r出现位置的引索。如果指定a,储存s中匹配表达式的那一部分 |
split(s,a [,r]) |
将s用FS字符或者正则表达式r(如果指定)分开放到数组a当中。返回字段总数 |
sprint(format,variables) |
用提供的format和variable返回一个类似printf输出的字符串 |
sub(r,s [,t]) |
在$0或者t中寻找正则表达式r的匹配。如果找到了就用s替换掉第一处匹配 |
substr(s,i [,n]) |
返回s中从引索i开始的n个字符组成的字符串。如果没有提供n,就返回剩下所有 |
tolower(s) |
全部替换成小写 |
toupper(s) |
全部替换成大写 |
| 函数 | 描述 |
| mktime(datespec)
| 把一个按照YYYY MM DD MM SS [DST]
格式指定的日期转换称时间戳(格式同shell的data函数 |
| strftime(format [,timestamp])
| 将当前时间戳(或者提供的timestamp)转换称格式化日期 |
| systime()
| 返回当前时间戳 |
function name([variables])
{
statement
}
gawk '
function myprint()
{
printf "%-16 - %s\n" ,$1 ,$4
}
BEGIN{FS="\n";RS=""}
{myprint()}
可以通过-f参数指定加载的gawk命令文件
正则表达式通过正则表达式引擎实现。Linux中,有两种流行的正则表达式引擎:
大多数linux工具都符合BRE标准。sed使用BRE标准,gawk使用ERE标准。
grep -rn "test" .
这一类使用BRE当中的纯文本匹配。在文件流当中寻找包含test字符串的位置。空格也被视为普通字符。在纯文本模式下会区分大小写。
正则表达式识别的特殊字符包括:.*[]^${}\+?|()
如果想要把某一个特殊字符作为纯文本匹配当中的普通字符,需要使用反斜杠进行转义。这一点对于grep,sed,gawk等工具都适用。
当指定正则表达式模式时,模式出现在任何地方都可以匹配。有两个特殊字符可以锁定模式在行尾或者行首。
锁定行首
脱字符(^)
定义数据流行首开始模式。如果模式出现在除了行首其他位置,正则表达式无法匹配。例如:
echo "Books are great" | sed -n '/^Book/p'
要使用脱字符,必须把脱字符放在模式的开头位置,如果不是开头位置,脱字符就自动变成了普通字符,例如:
echo "hh^" | sed -n '/h^/p'
这种情况BRE自动把它当成普通字符。这种情况甚至不需要进行转义。
锁定行尾
美元符($)
定义了行尾锚点。将这个特殊字符放在文本模式最后表示数据行必须是以该文本模式结尾。例如:
echo "it is a good book" | sed -n '/book$/p'
组合锚点
常见一些情况,行尾锚点和行首锚点同时使用,表示匹配符合某个文本模式的行:
echo "this is a book" | sed -n '/^this is a book$/p'
如果想要删掉所有的空白行,可以使用这种方式:
sed -n '/^$/d' test.txt
点号字符匹配处理换行符以外的任何一个单个字符。如果点号字符位置没有字符,模式就不会成立。空格也是算作一个字符
sed -n '/.at/p' data.txt
这样子就匹配到了at不是行首的所有行。
点号可以匹配任意字符,但是如果你想要限定匹配的具体字符,可以使用字符组。
方括号可以用来定义一个字符组。然后你可以在模式当中使用整个组,就像其他通配符一样。
sed -n '[ch]at/p' data.txt
这样子只能匹配cat和hat
字符组当中可以使用字母和数字和其他非特殊字符
可以在字符组开始加入一个脱字符实现排除字符组
sed -n '/[^ch]at/p' data.txt
这样子会去掉所有的cat和hat
在字符组当中字母和数字可以使用区间字符也就是单破折号字符表示一个字符区间,例如
sed -n '/[0-9]/p' data.txt
这样就可以表示所有包含数字的行
sed -n '/[a-ch-m]at/p' data.txt
这样子不允许出现d-g的字符
BRE当中定义了特殊字符组可以匹配特定类型字符
组 | 描述 |
---|---|
[[:alpha:]] |
任意字母,无论大小写 |
[[:alnum:]] |
任意数字和字母。0-9 a-z A-Z |
[[:blank:]] |
空格与制表符 |
[[:digit:]] |
0-9 |
[[:lower:]] |
a-z |
[[:print:]] |
所有可以打印的字符 |
[[:punct:]] |
匹配标点字符 |
[[:space:]] |
空白字符:空格,制表符,NL,FF,VT,CR |
[[:upper:]] |
A-Z |
可以和普通字符组一同使用
字符后面放置星号表明该字符必须匹配模式文本中出现0次或者多次
例如grep "test*" .
表示t可以出现一次或者很多次。也就是可以匹配testtt,test,和tes。
另一个方便的特性就是点号和星号结合使用,这样子有匹配任意数量的任意普通字符。通常用于数据流当中两个可能相邻或者不相邻的文本字符串之间。
echo "this is a good man" | sed -n '/is.*man/p'
这也可以同样适用于字符组和字符区间
sed只使用BRE,gawk使用ERE
问号类似于星号,只不过前面的字符只能出现零次或者1次。前面如果是字符组或者区间也是同样适用。
例如:
gawk '/be?t/{print $0}' test.txt
可以匹配bet,bt。但是不能匹配beet
加号类似于问好,但是加号前面字符只能出现1次或者多次。也就是必须至少出现一次。如果没有出现,就不会字符匹配。
gawk '/be+t/{print $0}'
这样子可以匹配bet,beet,beeet,但是不能匹配bt
ERE当中的花括号允许你为可重复的正则表达式指定一个上限。这通常叫做interval。可以使用两种格式指定区间
默认情况下,gawk不会识别正则表达式间隔,必须指定--re-interval
echo "bet" | gawk --re-interval '/be{1}t/{print $0}'
echo "bet" | gawk --re-interval '/b[ae]{1}t/{print $0}'
通过这种方式指定模式的出现次数
管道允许你检查数据流时候,使用OR的方式指定正则表达式引擎要用的两个或者多个模式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本匹配失败。
使用管道格式如下:
expr1|expr2|...
这是一个例子:
echo "The cat is asleep" | gawk ‘/cat|dog/{print $0}'
正则表达式和管道之间不能有空格,不然也是模式的一部分。
正则表达式也可以使用圆括号进行分组。分组之后,改组会被视为一个标准字符。可以像对普通字符一样使用这些特殊字符。例如:
echo -e "Sat\nSaturday" | gawk '/Sat(urday)?/{print $0}'
这种情况下,两行都可以被匹配成功。
分组和管道一起使用可以创建更多的模式匹配组:
echo "cat" | gawk '/(c|b)a(b|t)/print $0'
这样子的gawk可以自动的识别所有的cat,bat,cab,bab。
假设两个主机信息如下:
ServerA:10.1.0.1 不存在mysql服务
ServerB:10.1.0.2 安装mysql服务并且监听3306端口
现在,我们在serverA当中执行ssh -L 9906:10.1.0.2:3306 [email protected]
就可以建立一个在ServerA和ServerB当中的隧道,下面解释一下命令的含意:
-L
表示使用本地转发建立ssh隧道也就是本地端口的数据会被转发到目标主机的对应端口所以这个命令含义就是本地9906上面的数据会被转发到10.1.0.2的3306端口上面。此时我们通过mysql命令访问serverA的本地回环的9906端口,就相当于访问ServerB的mysql服务。这样做同时也会对传输的数据进行加密,达到数据安全性的保障。
但是通常我们只想建立隧道,不想使用这个ssh连接(直接退出连接会导致ssh隧道消失),这时就需要配合-N
,-f
选项了。
-N
会使得建立一个ssh隧道同时不打开ssh会话-f
会使得后台运行ssh隧道所以只需要在建立ssh隧道同时添加-Nf
就行了。
如果不想使用ServerA的回环地址而是其他网卡的地址可以使用以下命令指定监听的ip
ssh -fN -L 10.1.0.1:9906:10.1.0.2:3306 [email protected]
和本地转发相对应,远程转发就是把本地的端口映射到远程(本地转发是吧远程端口映射到本地)
ssh -fN -R 9906:10.1.0.2:3306 [email protected]
之后10.1.0.1的9906端口就会被监听,可以通过该端口访问10.1.0.2的3306端口的服务。
我们举一个现实的例子。我想通过dian.org.cn连接内网服务器192.168.0.66
ssh -Nf -L 12345:192.168.0.66:22022 [email protected] -p2222
这样192.168.0.66的22022端口就被映射到我的电脑本地的localhost:12345了