|
用户名:chenbin 笔名:chenbin 地区: |
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
常用脚本命令集
| 代码: |
| #!/bin/bash if [ $# -ne 2 ]; then echo 'Usage: split file size(in bytes)' exit fi file=$1 size=$2 if [ ! -f $file ]; then echo "$file doesn't exist" exit fi #TODO: test if $size is a valid integer filesize=`/bin/ls -l $file | awk '{print $5}'` echo filesize: $filesize let pieces=$filesize/$size let remain=$filesize-$pieces*$size if [ $remain -gt 0 ]; then let pieces=$pieces+1 fi echo pieces: $pieces i=0 while [ $i -lt $pieces ]; do echo split: $file.$i: dd if=$file of=$file.$i bs=$size count=1 skip=$i let i=$i+1 done echo "#!/bin/bash" > merge echo "i=0" >> merge echo "while [ $i -lt $pieces ];" >> merge echo "do" >> merge echo " echo merge: $file.$i" >> merge echo " if [ ! -f $file.$i ]; then" >> merge echo " echo merge: $file.$i missed" >> merge echo " rm -f $file.merged" >> merge echo " exit" >> merge echo " fi" >> merge echo " dd if=$file.$i of=$file.merged bs=$size count=1 seek=$i" >> merge echo " let i=$i+1" >> merge echo "done" >> merge chmod u+x merge' |
| 代码: |
#!/bin/sh # The right of usage, distribution and modification is here by granted by the author. # The author deny any responsibilities and liabilities related to the code. # OK=0 A=`find $1 -print` if expr $3 == 1 >/dev/null ; then M=Jan ; OK=1 ; fi if expr $3 == 2 >/dev/null ; then M=Feb ; OK=1 ; fi if expr $3 == 3 >/dev/null ; then M=Mar ; OK=1 ; fi if expr $3 == 4 >/dev/null ; then M=Apr ; OK=1 ; fi if expr $3 == 5 >/dev/null ; then M=May ; OK=1 ; fi if expr $3 == 6 >/dev/null ; then M=Jun ; OK=1 ; fi if expr $3 == 7 >/dev/null ; then M=Jul ; OK=1 ; fi if expr $3 == 8 >/dev/null ; then M=Aug ; OK=1 ; fi if expr $3 == 9 >/dev/null ; then M=Sep ; OK=1 ; fi if expr $3 == 10 >/dev/null ; then M=Oct ; OK=1 ; fi if expr $3 == 11 >/dev/null ; then M=Nov ; OK=1 ; fi if expr $3 == 12 >/dev/null ; then M=Dec ; OK=1 ; fi if expr $3 == 1 >/dev/null ; then M=Jan ; OK=1 ; fi if expr $OK == 1 > /dev/null ; then ls -l --full-time $A 2>/dev/null | grep "$M $4" | grep $2 ; else echo Usage: $0 path Year Month Day; echo Example: $0 ~ 1998 6 30; fi |
| 代码: |
# ls -l * |grep "^-"|wc -l ---- to count files # ls -l * |grep "^d"|wc -l ----- to count dir |
| 代码: |
ls -F | grep /$ 或者 alias sub = "ls -F | grep /$"(linux) ls -l | grep "^d" 或者 ls -lL | grep "^d" (Solaris) |
| 代码: |
如果你只想看文件的前5行,可以使用head命令, 如: head -5 /etc/passwd 如果你想查看文件的后10行,可以使用tail命令, 如: tail -10 /etc/passwd 你知道怎么查看文件中间一段吗?你可以使用sed命令 如: sed -n '5,10p' /etc/passwd 这样你就可以只查看文件的第5行到第10行。 |
| 代码: |
例如查找当前目录下含有"the string you want find..."字符串的文件: $find . -type f -exec grep “the string you want find...” {} ; -print |
| 代码: |
下面的短小的shell程序可以列出目录树, 充分利用了sed强大的模式匹配能力. 目录树形式如下: . `----shellp `----updates `----wu-ftpd-2.4 | `----doc | | `----examples | `----src | | `----config | | `----makefiles | `----support | | `----makefiles | | `----man | `----util 脚本如下: #!/bin/sh # dtree: Usage: dtree [any directory] dir=${1:-.} (cd $dir; pwd) find $dir -type d -print | sort -f | sed -e "s,^$1,," -e "/^$/d" -e "s,[^/]*/([^/]*)$,`----1," -e "s,[^/]*/,| ,g" |
| 代码: |
我们经常会遇到需要取出分字段的文件的某些特定字段,例如/etc/password就是通过“:”分隔各个字段的。可以通过cut命令来实现。例如,我们希望将系统账号名保存到特定的文件,就可以: cut -d: -f 1 /etc/passwd > /tmp/users -d用来定义分隔符,默认为tab键,-f表示需要取得哪个字段。 当然也可以通过cut取得文件中每行中特定的几个字符,例如: cut -c3-5 /etc/passwd 就是输出/etc/passwd文件中每行的第三到第五个字符。 -c 和 -f 参数可以跟以下子参数: N 第N个字符或字段 N- 从第一个字符或字段到文件结束 N-M 从第N个到第M个字符或字段 -M 从第一个到第N个字符或字段 |
| 代码: |
密码中还是不能带空格,不管了,能加密就好,先这么用着。 ============================================================ #!/bin/bash # Encrypt file with vim if (test $# -lt 2) then echo Usage: decrypt password filename else vim -e -s -c ":set key=$1" -c ':wq' $2 echo "$2 encrypted." fi ============================================================ [weeder@SMTH weeder]$ for file in *.txt ; do encrypt test $file ; done test2.txt encrypted. test4.txt encrypted. test9.txt encrypted. kick.txt encrypted. echo "$2 encrypted." fi [weeder@SMTH weeder]$ for file in *.txt ; do encrypt test $file ; done test2.txt encrypted. test4.txt encrypted. test9.txt encrypted. kick.txt encrypted. too_old.txt encrypted. too_old_again.txt encrypted. bg5.txt encrypted. [weeder@SMTH weeder]$ |
| 代码: |
在shell脚本的实际编写中,有一些特殊的变量十分有用: $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数。与位置变量不同,此选项参数可超过9个 $$ 脚本运行的当前进程ID号 $! 后台运行的最后一个进程的进程ID号 $@ 与$#相同,但是使用时加引号,并在引号中返回每个参数 $- 显示shell使用的当前选项,与set命令功能相同 $? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
| 代码: |
program_name |tee logfile 这样程序执行期间的显示都记录到logfile同时显示到标准输出(屏幕)。 |
| 代码: |
首先在aliases文件里面建立一个alias: alluser: :include:/etc/mail/allusers 并执行newaliases使之生效,然后在/etc/mail/allusers里面列出所有用户,可以使用下面的命令: awk -F: '$3 > 100 { print $1 }' /etc/passwd > /etc/mail/allusers |
| 代码: |
在制作自己的发行版时经常需要判断某条命令需要哪些库文件的支持,以确保指定的命令在独立的系统内可以可靠的运行。 在Linux环境下通过ldd命令即可实现,在控制台执行: ldd /bin/ls 即可得到/bin/ls命令的相关库文件列表。 |
| 代码: |
Host能够用来查询域名,然而它可以得到更多的信息。host -t mx linux.com可以查询出Linux.com的MX记录,以及处理Mail的Host的名字。Host -l linux.com会返回所有注册在linux.com下的域名。host -a linux.com则会显示这个主机的所有域名信息。 |
| 代码: |
以下是脚本: echo "系统当前用户" echo "---------------" who | awk '{print $2}' echo "---------------" echo "输入要杀死终端的终端号:" read $TTY kill -9 ${K}=`ps -t $TTY | grep [0-9] | awk '{print $1}'` |
整合Apache+Tomcat
Windows组策略应用全攻略
在Windows server2003下创建软RAID完全手册
六大软件伤硬盘
Zeus+php+Zend Optimizer for Linux安装指南
本文是Crossday第一篇unix作文,基于linux而写出。zeus是一个著名的web服务器软件,其效能远在apache1/2之上,是整台服务器用户跑论坛的首选,对内存小于1G而流量非常大的用户,换用zeus会比apache多获得一倍的负载能力。
本文提供方法在redhat linux 7.3及以上各版本,PHP 4.3.0及以上版本,Zend Optimizer 2.1.0验证通过,所配置参数为适应每15分钟在线1000人以上的,使用Discuz! 2.0/3.0的中大型论坛所优化
进入一个目录 如
cd /usr/local/src
找一个zeus 4.2 r3 for linux的破解版本下载
wget http://download.discuz.net/zeus_linux.tgz
tar xvzf zeus_linux.tgz
cd Zeus.Web.Server.v4.2r3.Linux.x86/
tar xvzf Zeus_42r3_Linux-glibc2.1.tgz
cd Zeus_42r3_Linux-glibc2.1/
开始安装
./zinstall
按CTRL+C 打入accept
Where should the product be installed? [/usr/local/zeus]: 回车
Installation options:
1. Full install of both admin server & web server
2. Stand-alone install of admin server
3. Clustered install of web server
H. Help
Choose installation option [1]: 回车
Enter the key filename, or leave blank for unlicensed mode: /usr/local/src/Zeus.Web.Server.v4.2r3.Linux.x86/license.key (输入密匙位置)
Please choose a password for the admin server: ****** (输入管理员密码)
Re-enter: ****** (重新输入密码)
Would you like Zeus to start at boot time? Y/N [Y]: (回车)
以上是安装过程 很容易的就装好了 如果有apache在跑 先把他停掉 避免80端口冲突
/usr/local/apache/bin/apachectl stop
若安装中提示libdb.so.2不存在 先进入压缩包的libdb.so.2目录中
rpm -ivh db1-1.85-8.i386.rpm
安装php
cd /usr/local/src
从小日本网站下载php
wget http://cn2.php.net/get/php-4.3.4.tar.gz/from/jp2.php.net/mirror
tar xvzf php-4.3.4.tar.gz
cd php-4.3.4/
./configure --prefix=/usr/local/php --enable-fastcgi --enable-force-cgi-redirect --with-config-file-path=/etc --with-zlib --with-mysql --with-xml
make
make install
cp php.ini-dist /etc/php.ini
安装Zend Optimizer
cd /usr/local/src
wget http://download.discuz.net/ZendOptimizer-2%5B1%5D.1.0b-Linux_glibc21-i386.tar.gz
tar xvzf ZendOptimizer-2%5B1%5D.1.0b-Linux_glibc21-i386.tar.gz
cd ZendOptimizer-2.1.0b-Linux_glibc21-i386/
./install.sh
一阵回车 问Confirm the location of your php.ini file的时候打入/etc
问Are you using Apache web server?的时候选No
设置Zeus
访问 http://your.com:9090 (你的域名地址或IP地址)
用户名 admin 密码:刚才设定的密码
进入Zeus管理控制台,点Create a Virtual Server 把相关的六个选项填好 不多说
提交后出现刚才加入的virtual server,点FastCGI
Enabling Support for FastCGI Programs 选 Enabled
Restricting FastCGI locations 选 Enable FastCGI programs to be located anywhere
Configuring Directories for FastCGI Local Responders 不用写他 因为我们用remote方式连接fastcgi
Configuring FastCGI Remote Responders 中填入以下配置
Directory name : /usr/local/php/bin/php
Location: Machine name: localhost
Port: 8002
Additional methods supported?:None
其他都不用管,点Apply changes
点左边Url Handling下面的Handlers
File Extension : php
Specify the path and filename of the handler, relative to the document root : /usr/local/php/bin/php
点Apply changes
点左边Url Handling下面的Directory Requests
Index Files中加入一个 index.php 和一分隔逗号
Directory Listing选Disabled
点Apply changes
再点上面的 This configuration has been modified. Click here for details or to make it take effect.
出来的页面点Commit
配置php-fastcgi
cd /usr/local/zeus/rc.d
建立一个S05php的文件 内容为
Code: [Copy to clipboard]
#!/bin/sh
# Script to start and stop the persistent PHP runner for FastCGI.
# Please check paths before use.
# FastCGI PHP binary
FPHPBIN=/usr/local/php/bin/php
# Location to place semaphore
SEMFILE=/tmp/php.pid
PHP_FCGI_CHILDREN=200
PHP_FCGI_MAX_REQUESTS=1000
export PHP_FCGI_CHILDREN
export PHP_FCGI_MAX_REQUESTS
# This is Linux - use /proc to increase the local (ephemeral) port range
#echo 1024 65000 > /proc/sys/net/ipv4/ip_local_port_range
if [ -z "$ZEUSHOME" ]
then
cd `dirname $0`/..
ZEUSHOME=`pwd`
export ZEUSHOME
fi
case "$1" in
'start')
if [ -e $SEMFILE ]
then
echo FastCGI PHP error: already running.Restart FastCGI PHP now
kill `cat $SEMFILE`
sleep 5
fi
if [ ! -x $FPHPBIN ]
then
echo FastCGI PHP error: please check that $FPHPBIN is executable and exists.
exit 1
fi
echo Starting FastCGI PHP.
$ZEUSHOME/web/bin/fcgirunner --user=99 --group=99 --pidfile=$SEMFILE 8002 $FPHPBIN
;;
'stop')
if [ -e $SEMFILE ]
then
echo Stopping FastCGI PHP.
kill `cat $SEMFILE`
rm $SEMFILE
exit 0
fi
;;
'restart')
if [ -e $SEMFILE ]
then
echo Stopping FastCGI PHP.
kill `cat $SEMFILE`
sleep 5
fi
echo Starting FastCGI PHP.
$ZEUSHOME/web/bin/fcgirunner --user=99 --group=99 --pidfile=$SEMFILE 8002 $FPHPBIN
;;
*)
echo "usage: $0 {start|stop|restart}"
;;
esac
exit 1
注意--user=65534 --group=65534 为php进程运行的用户和组,一般设置为nobody用户和组FreeBSD是65534/65534,Linux是99/99
如不会建文件 可看下面范例
vi S05php
(打一个小写的i)
(把上面的内容复制到剪贴板 然后按SHIFT+INSERT粘贴过去)
(按ESC)
(输入 :wq 然后回车)
改写属性使其可执行
chmod 755 S05php
回到Zeus的9090端口管理界面 把刚才设置的虚拟主机Start
启动Zeus的命令为 /usr/local/zeus/start-zeus
停止Zeus的命令为 /usr/local/zeus/stop-zeus
重启Zeus的命令为 /usr/local/zeus/restart-zeus
编辑/etc/rc.local
加入一行
/usr/local/zeus/start-zeus
使得Zeus在开机时自动运行
如果现在无法访问web,多半是因为没有把apache停掉造成的,注意要将启动时候的apache自启动给去掉 否则zeus无法绑定到80端口进而无法访问 如没有上述问题 安装后仍然无法访问 可能为web服务器没有正确安装 例如第一次安装后中断退出 第二次又以升级方式安装 等等 这样的情况建议先把zeus停掉 然后将其目录rm -fr删除 重新安装一次即可正常 个人以为这是zeus安装脚本设计的不合理所致
结束:zeus实在是太强大了,无以言表,在不需任何外挂模块的情况下实现多线程的高效服务 IP并发限制 来路限制 带宽限制等如此多丰富而实用的功能,确实是unix世界中极为强悍和高效的web服务器软件
S05php脚本为CCF的坛主hunrea所作
AWK:Linux 管理员的智能工具包
AWK 实用工具带有其自己的自包含语言,它不仅是 Linux 中也是任何环境中现有的功能最强大的数据处理引擎之一。这种编程及数据操作语言(其名称得自于它的创始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母)的最大功能取决于一个人所拥有的知识。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
AWK 是什么?
最简单地说,AWK 是一种用于处理文本的编程语言工具。AWK 实用工具的语言在很多方面类似于 shell 编程语言,尽管 AWK 具有完全属于其本身的语法。在最初创造 AWK 时,其目的是用于文本处理,并且这种语言的基础是,只要在输入数据中有模式匹配,就执行一系列指令。该实用工具扫描文件中的每一行,查找与命令行中所给定内容相匹配的模式。如果发现匹配内容,则进行下一个编程步骤。如果找不到匹配内容,则继续处理下一行。
尽管操作可能会很复杂,但命令的语法始终是:
其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号 ({}) 不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。
了解字段
实用工具将每个输入行分为记录和字段。记录是单行的输入,而每条记录包含若干字段。默认的字段分隔符是空格或制表符,而记录的分隔符是换行。虽然在默认情况下将制表符和空格都看作字段分隔符(多个空格仍然作为一个分隔符),但是可以将分隔符从空格改为任何其它字符。
为了进行演示,请查看以下保存为 emp_names 的员工列表文件:
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
当 AWK 读取输入内容时,整条记录被分配给变量 $0。每个字段以字段分隔符分开,被分配给变量 $1、$2、$3 等等。一行在本质上可以包含无数个字段,通过字段号来访问每个字段。因此,命令
awk '{print $1,$2,$3,$4,$5}' names
将会产生的打印输出是
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
值得注意的一项重要内容是,AWK 解释由空格分隔的五个字段,但当它打印显示内容时,在每个字段间只有一个空格。利用为每个字段指定了唯一号码的功能,您可以选择只打印特定的字段。例如,只打印每条记录的姓名时,只需选择第二个和第三个字段进行打印:
$ awk '{print $2,$3}' emp_names
DULANEY EVAN
DURHAM JEFF
STEEN BILL
FELDMAN EVAN
SWIM STEVE
BOGUE ROBERT
JUNE MICAH
KANE SHERYL
WOOD WILLIAM
FERGUS SARAH
BUCK SARAH
TUTTLE BOB
$
您还可以指定按任何顺序打印字段,而无论它们在记录中是如何存在的。因此,只需要显示姓名字段,并且使其顺序颠倒,先显示名字再显示姓氏:
$ awk '{print $3,$2}' emp_names
EVAN DULANEY
JEFF DURHAM
BILL STEEN
EVAN FELDMAN
STEVE SWIM
ROBERT BOGUE
MICAH JUNE
SHERYL KANE
WILLIAM WOOD
SARAH FERGUS
SARAH BUCK
BOB TUTTLE
$
使用模式
通过包含一个必须匹配的模式,您可以选择只对特定的记录而不是所有的记录进行操作。模式匹配的最简单形式是搜索,其中要匹配的项目被包含在斜线 (/pattern/) 中。例如,只对那些居住在阿拉巴马州的员工执行前面的操作:
$ awk '/AL/ {print $3,$2}' emp_names
EVAN DULANEY
JEFF DURHAM
BILL STEEN
EVAN FELDMAN
STEVE SWIM
$
如果您不指定要打印的字段,则会打印整个匹配的条目:
$ awk '/AL/' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
$
对同一数据集的多个命令可以用分号 (;) 分隔开。例如,在一行中打印姓名,而在另一行中打印城市和州名:
$ awk '/AL/ {print $3,$2 ; print $4,$5}' emp_names
EVAN DULANEY
MOBILE AL
JEFF DURHAM
MOBILE AL
BILL STEEN
MOBILE AL
EVAN FELDMAN
MOBILE AL
STEVE SWIM
UNKNOWN AL
$
如果没有使用分号 (print $3,$2,$4,$5),则会在同一行中显示所有内容。另一方面,如果分别给出两个打印语句,则会产生完全不同的结果:
$ awk '/AL/ {print $3,$2} {print $4,$5}' emp_names
EVAN DULANEY
MOBILE AL
JEFF DURHAM
MOBILE AL
BILL STEEN
MOBILE AL
EVAN FELDMAN
MOBILE AL
STEVE SWIM
UNKNOWN AL
PHOENIX AZ
PHOENIX AZ
UNKNOWN AR
MUNCIE IN
MUNCIE IN
MUNCIE IN
MUNCIE IN
$
只有在列表中找到 AL 时才会给出字段三和字段二。但是,字段四和字段五是无条件的,始终打印它们。只有第一组花括号中的命令对前面紧邻的命令 (/AL/) 起作用。
结果非常不便于阅读,可以使其稍微更清晰一些。首先,在城市与州之间插入一个空格和逗号。然后,在每两行显示之后放置一个空行:
$ awk '/AL/ {print $3,$2 ; print $4", "$5"
"}' emp_names
EVAN DULANEY
MOBILE, AL
JEFF DURHAM
MOBILE, AL
BILL STEEN
MOBILE, AL
EVAN FELDMAN
MOBILE, AL
STEVE SWIM
UNKNOWN, AL
$
在第四和第五个字段之间,添加一个逗号和一个空格(在引号之间),在第五个字段后面,打印一个换行符 (
)。在 AWK 打印语句中还可以使用那些可在 echo 命令中使用的所有特殊字符,包括:
(换行)
(制表)
(退格)
f(进纸)
(回车)
因此,要读取全部五个最初由制表符分隔开的字段,并且也利用制表符打印它们,您可以编程如下
$ awk '{print $1" "$2" "$3" "$4" "$5}' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
$
通过连续设置多项标准并用管道 (|) 符号将其分隔开,您可以一次搜索多个模式匹配:
$ awk '/AL|IN/' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
$
这样可找到每个阿拉巴马州和印第安那州居民的匹配记录。但是在试图找出居住在亚利桑那州的人时,出现了一个问题:
$ awk '/AR/' emp_names
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AZ
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
$
员工 46026 和 46027 没有住在亚利桑那州;但是他们的名字中包含所搜索的字符序列。切记,当在 AWK 中进行模式匹配时,例如 grep、sed 或者大部分其他 Linux/Unix 命令,将在记录(行)中的任何位置查找匹配,除非指定进行其他操作。为解决这一问题,必须将搜索与特定字段联系起来。通过利用代字号 (?) 以及对特定字段的说明,可以达到这一目的,如下例所示:
$ awk '$5 ? /AR/' emp_names
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AZ
$
代字号(表示匹配)的对应符号是一个前面带有感叹号的代字号 (!?)。这些字符通知程序,如果搜索序列没有出现在指定字段中,则找出与搜索序列相匹配的所有行:
$ awk '$5 !? /AR/' names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
$
在这种情况下,将显示第五个字段中没有 AR 的所有行 — 包括两个 Sarah 条目,这两个条目确实包含 AR,但却是在第三个字段而不是第五个字段中。
花括号和字段分隔符
括号字符在 AWK 命令中起着很重要的作用。出现在括号之间的操作指出将要发生什么以及何时发生。当只使用一对括号时:
{print $3,$2}
括号间的所有操作同时发生。当使用多于一对的括号时:
{print $3}{print $2}
执行第一组命令,在该命令完成后执行第二组命令。注意以下两列清单的区别:
$ awk '{print $3,$2}' names
EVAN DULANEY
JEFF DURHAM
BILL STEEN
EVAN FELDMAN
STEVE SWIM
ROBERT BOGUE
MICAH JUNE
SHERYL KANE
WILLIAM WOOD
SARAH FERGUS
SARAH BUCK
BOB TUTTLE
$
$ awk '{print $3}{print $2}' names
EVAN
DULANEY
JEFF
DURHAM
BILL
STEEN
EVAN
FELDMAN
STEVE
SWIM
ROBERT
BOGUE
MICAH
JUNE
SHERYL
KANE
WILLIAM
WOOD
SARAH
FERGUS
SARAH
BUCK
BOB
TUTTLE
$
要利用多组括号进行重复查找,执行第一组中的命令直到完成为止;然后处理第二组命令。如果有第三组命令,则在第二组命令完成后执行它,以此类推。在所生成的打印输出中,有两个分隔的打印命令,因此先执行第一个命令,随后执行第二个命令,这样导致每个条目显示在两行而不是一行中。
区分两个字段的字段分隔符不一定始终是空格;它可以是任何可识别的字符。为进行演示,假定 emp_names 文件利用冒号而不是制表符来分隔字段:
$ cat emp_names
46012:DULANEY:EVAN:MOBILE:AL
46013:DURHAM:JEFF:MOBILE:AL
46015:STEEN:BILL:MOBILE:AL
46017:FELDMAN:EVAN:MOBILE:AL
46018:SWIM:STEVE:UNKNOWN:AL
46019:BOGUE:ROBERT:PHOENIX:AZ
46021:JUNE:MICAH:PHOENIX:AZ
46022:KANE:SHERYL:UNKNOWN:AR
46024:WOOD:WILLIAM:MUNCIE:IN
46026:FERGUS:SARAH:MUNCIE:IN
46027:BUCK:SARAH:MUNCIE:IN
46029:TUTTLE:BOB:MUNCIE:IN
$
如果试图通过指定所需要的第二个字段来打印姓氏
$ awk '{print $2}' emp_names
您最后会得到十二个空行。因为文件中没有空格,除了第一个字段之外没有可认别的字段。为解决这一问题,必须通知 AWK 是空格之外的另一个字符作为分隔符,有两种方法可通知 AWK 使用新的字段分隔符:使用命令行参数 -F,或在程序中指定变量 FS。两种方法的效果相同,只有一种例外情况,如下例所示:
$ awk '{FS=":"}{print $2}' emp_names
DURHAM
STEEN
FELDMAN
SWIM
BOGUE
JUNE
KANE
WOOD
FERGUS
BUCK
TUTTLE
$
$ awk -F: '{print $2}' emp_names
DULANEY
DURHAM
STEEN
FELDMAN
SWIM
BOGUE
JUNE
KANE
WOOD
FERGUS
BUCK
TUTTLE
$
在第一个命令中,头一条记录返回不正确的空行,而其他结果正确。直到读取第二条记录时,才识别字段分隔符并正确地执行。通过使用 BEGIN 语句可以纠正这一缺点(在后文详述)。-F 的功能非常类似于 BEGIN,能够正确地读取第一条记录并按要求执行。
在本文开始处我曾提到,默认的显示/输出字段分隔符是空格。通过使用输出字段分隔符 (OFS) 变量,可以在程序中更改此特性。例如,要读取文件(由冒号分隔)并以短划线显示,则命令是
$ awk -F":" '{OFS="-"}{print $1,$2,$3,$4,$5}' emp_names
46012-DULANEY-EVAN-MOBILE-AL
46013-DURHAM-JEFF-MOBILE-AL
46015-STEEN-BILL-MOBILE-AL
46017-FELDMAN-EVAN-MOBILE-AL
46018-SWIM-STEVE-UNKNOWN-AL
46019-BOGUE-ROBERT-PHOENIX-AZ
46021-JUNE-MICAH-PHOENIX-AZ
46022-KANE-SHERYL-UNKNOWN-AR
46024-WOOD-WILLIAM-MUNCIE-IN
46026-FERGUS-SARAH-MUNCIE-IN
46027-BUCK-SARAH-MUNCIE-IN
46029-TUTTLE-BOB-MUNCIE-IN
$
FS 和 OFS 是(输入)字段分隔符和输出字段分隔符,它们只是一对可以在 AWK 实用工具中使用的变量。例如,要在打印时为每行编号,可以采用以下方式使用 NR 变量:
$ awk -F":" '{print NR,$1,$2,$3}' emp_names
1 46012 DULANEY EVAN
2 46013 DURHAM JEFF
3 46015 STEEN BILL
4 46017 FELDMAN EVAN
5 46018 SWIM STEVE
6 46019 BOGUE ROBERT
7 46021 JUNE MICAH
8 46022 KANE SHERYL
9 46024 WOOD WILLIAM
10 46026 FERGUS SARAH
11 46027 BUCK SARAH
12 46029 TUTTLE BOB
$
找出员工号码处于 46012 和 46015 之间的所有行:
$ awk -F":" '/4601[2-5]/' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
$
添加文本
可以按照添加控制序列或其他字符的相同方式将文本添加到显示中。例如,要将分隔符从空格改为冒号,则命令是
awk '{print $1":"$2":"$3":"$4":"$5}' emp_names > new_emp_names
在这种情况下,字符 (:) 包含在引号 ("/") 中,它被添加到每个字段之间。在引号之间的值可以是任何内容。例如,创建一个关于居住在阿拉巴马州的员工的外观类似数据库的显示:
$ awk '$5 ~ /AL/ {print "NAME: "$2", "$3"
CITY-STATE:
"$4", "$5"
"}' emp_names
NAME: DULANEY, EVAN
CITY-STATE: MOBILE, AL
NAME: DURHAM, JEFF
CITY-STATE: MOBILE, AL
NAME: STEEN, BILL
CITY-STATE: MOBILE, AL
NAME: FELDMAN, EVAN
CITY-STATE: MOBILE, AL
NAME: SWIM, STEVE
CITY-STATE: UNKNOWN, AL
$
数学操作
AWK 除了提供文本功能,还提供全部范围的算术操作符,包括以下符号:
+ 将数字相加
- 减
* 乘
/ 除
^ 执行指数运算
% 提供模
++ 将变量值加一
+= 将其他操作的结果分配给变量
— 将变量减一
-= 将减法操作的结果分配给变量
*= 分配乘法操作的结果
/= 分配除法操作的结果
%= 分配求模操作的结果
例如,假定您的机器上存在以下的文件,详细地列出硬件商店中的物品:
$ cat inventory
hammers 5 7.99
drills 2 29.99
punches 7 3.59
drifts 2 4.09
bits 55 1.19
saws 123 14.99
nails 800 .19
screws 80 .29
brads 100 .24
$
第一项业务定单是通过将第二个字段(数量)的值乘以第三个字段(价格)的值,计算每种物品的库存价值:
$ awk '{print $1,"QTY: "$2,"PRICE: "$3,"TOTAL: "$2*$3}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95
drills QTY: 2 PRICE: 29.99 TOTAL: 59.98
punches QTY: 7 PRICE: 3.59 TOTAL: 25.13
drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18
bits QTY: 55 PRICE: 1.19 TOTAL: 65.45
saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77
nails QTY: 800 PRICE: .19 TOTAL: 152
screws QTY: 80 PRICE: .29 TOTAL: 23.2
brads QTY: 100 PRICE: .24 TOTAL: 24
$
如果这些行本身并不重要,您只是希望确定商店中有多少件物品,则可以分配一个普通变量,按照每条记录中的物品数量增加:
$ awk '{x=x+$2} {print x}' inventory
5
7
14
16
71
194
994
1074
1174
$
根据这一数据,商店中有 1174 件物品。第一次执行时,变量 x 没有值,因此它采用第一行第二个字段的值。第二次执行时,它保留了第一行的值并加上第二行的值,以此类推,直到达到累计的总合。
可以应用相同的过程来确定现有库存的总价值:
$ awk '{x=x+($2*$3)} {print x}' inventory
39.95
99.93
125.06
133.24
198.69
2042.46
2194.46
2217.66
2241.66
$
因此,1174 件物品的价值是 $2,241.66。虽然这一过程可以获得总计值,但它的外观很差,需要加工成实际的报表。利用一些附加项,很容易使显示变得更整洁:
$ awk '{x=x+($2*$3)}{print $1,"QTY: "$2,"PRICE: "$3,"TOTAL: "$2*$3,"BAL: "x}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95 BAL: 39.95
drills QTY: 2 PRICE: 29.99 TOTAL: 59.98 BAL: 99.93
punches QTY: 7 PRICE: 3.59 TOTAL: 25.13 BAL: 125.06
drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18 BAL: 133.24
bits QTY: 55 PRICE: 1.19 TOTAL: 65.45 BAL: 198.69
saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77 BAL: 2042.46
nails QTY: 800 PRICE: .19 TOTAL: 152 BAL: 2194.46
screws QTY: 80 PRICE: .29 TOTAL: 23.2 BAL: 2217.66
brads QTY: 100 PRICE: .24 TOTAL: 24 BAL: 2241.66
$
该过程提供了每条记录的清单,同时将总价值分配给库存值,并保持商店资产的运作平衡。
BEGIN 和 END
使用 BEGIN 和 END 语句可以分别指定在处理实际开始之前或者完成之后进行操作。BEGIN 语句最常用于建立变量或显示标题。另一方面,END 语句可用于在程序结束后继续进行处理。
在前面的示例中,利用以下例程生成了物品的总价值:
awk '{x=x+($2*$3)} {print x}' inventory
该例程在运行总计累加时显示了文件中的每一行。没有其他方法可以指定它,而不让在每一行进行打印也导致它始终不打印出来。但是,利用 END 语句可以避免这一问题:
$ awk '{x=x+($2*$3)} END {print "Total Value of Inventory:"x}' inventory
Total Value of Inventory: 2241.66
$
定义了变量 x,它对每一行进行处理;但是,在所有处理完成之前不会生成显示。尽管可以作为独立例程使用,它也可以置入到先前的代码列表,添加更多信息并生成更完整的报表:
$ awk '{x=x+($2*$3)} {print $1,"QTY: "$2,"PRICE:
"$3,"TOTAL: "$2*$3} END {print "Total Value of Inventory: " x}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95
drills QTY: 2 PRICE: 29.99 TOTAL: 59.98
punches QTY: 7 PRICE: 3.59 TOTAL: 25.13
drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18
bits QTY: 55 PRICE: 1.19 TOTAL: 65.45
saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77
nails QTY: 800 PRICE: .19 TOTAL: 152
screws QTY: 80 PRICE: .29 TOTAL: 23.2
brads QTY: 100 PRICE: .24 TOTAL: 24
Total Value of Inventory: 2241.66
$
BEGIN 命令与 END 的工作方式相同,但它建立了那些需要在完成其他工作之前所做的项目。该过程最常见的目的是创建报表的标题。此例程的语法类似于
$ awk 'BEGIN {print "ITEM QUANTITY PRICE TOTAL"}'
输入、输出和源文件
AWK 工具可以从文件中读取其输入,正如在此之前所有示例所做的那样,它也可以从其他命令的输出中获取输入。例如:
$ sort emp_names | awk '{print $3,$2}'
awk 命令的输入是排序操作的输出。除了 sort,还可以使用任何其他的 Linux 命令 — 例如 grep。该过程允许您在离开所选定字段前对文件执行其他操作。
类似于解释程序,AWK 使用输出改向操作符 > 和 >> 将其输出放入文件中而不是标准输出设备。这些符号的作用类似于它们在解释程序中的对应符号,因此 > 在不存在文件时创建文件,而 >> 追加到现有文件的尾部。请看以下的示例:
$ awk '{print NR, $1 ) > "/tmp/filez" }' emp_names
$ cat /tmp/filez
1 46012
2 46013
3 46015
4 46017
5 46018
6 46019
7 46021
8 46022
9 46024
10 46026
11 46027
12 46029
$
检查该语句的语法,您会看到输出改向是在打印语句完成后进行的。必须将文件名包含在引号中,否则它只是一个未初始化的 AWK 变量,而将指令联接起来会在 AWK 中产生错误。(如果不正确地使用改向符号,则 AWK 无法了解该符号意味着“改向”还是一个关系操作符。)
在 AWK 中输出到管道也类似于解释程序中所实现的相同操作。要将打印命令的输出发送到管道中,可以在打印命令后附加管道符号以及命令的名称,如下所示:
$ awk '{ print $2 | "sort" }' emp_names
BOGUE
BUCK
DULANEY
DURHAM
FELDMAN
FERGUS
JUNE
KANE
STEEN
SWIM
TUTTLE
WOOD
$
这是输出改向的情况,必须将命令包含在引号中,而管道的名称是被执行命令的名称。
AWK 所使用的命令可以来自两个地方。首先,可以在命令行中指定它们,如示例中所示。其次,它们可以由源文件提供。如果是这种情况,通过 -f 选项将这种情况向 AWK 发出警告。演示如下:
$ cat awklist
{print $3,$2}
{print $4,$5,"
"}
$
$ awk -f awklist emp_names
EVAN DULANEY
MOBILE AL
JEFF DURHAM
MOBILE AL
BILL STEEN
MOBILE AL
EVAN FELDMAN
MOBILE AL
STEVE SWIM
UNKNOWN AL
ROBERT BOGUE
PHOENIX AZ
MICAH JUNE
PHOENIX AZ
SHERYL KANE
UNKNOWN AR
WILLIAM WOOD
MUNCIE IN
SARAH FERGUS
MUNCIE IN
SARAH BUCK
MUNCIE IN
BOB TUTTLE
MUNCIE IN
$
注意,在源文件中的任何地方或者在命令行中调用它时,不使用单引号。单引号只用于区别命令行中的命令与文件名称。
如果简单的输出不能处理您的程序中所需要的复杂信息,则可以尝试由 printf 命令获得的更加复杂的输出,其语法是
printf( format, value, value ...)
该语法类似于 C 语言中的 printf 命令,而格式的规格是相同的。通过插入一项定义如何打印数值的规格,可以定义该格式。格式规格包含一个跟有字母的 %。类似于打印命令,printf 不必包含在圆括号中,但是可以认为使用圆括号是一种良好的习惯。
下表列出 printf 命令提供的各种规格。
规格 说明
%c 打印单个 ASCII 字符
%d 打印十进制数
%e 打印数字的科学计数表示
%f 打印浮点表示
%g 打印 %e 或 %f;两种方式都更简短
%o 打印无符号的八进制数
s 打印 ASCII 字符串
%x 打印无符号的十六进制数
%% 打印百分号;不执行转换
可以在 % 与字符之间提供某些附加的格式化参数。这些参数进一步改进数值的打印方式:
参数 说明
- 将字段中的表达式向左对齐
,width 根据需要将字段补齐到指定宽度(前导零使用零将字段补齐)
.prec 小数点右面数字的最大字符串宽度或最大数量
printf 命令能够控制并将数值从一种格式转换为另一种格式。当需要打印变量的值时,只需提供一种规格,指示 printf 如何打印信息(通常包含在双引号中)即可。必须为每个传递到 printf 的变量包含一个规格参数;如果包含过少的参数,则 printf 不会打印所有的数值。
处理错误
AWK 工具报告所发生错误的方式很令人恼火。一个错误会阻碍任何操作的进行,所提供的错误信息非常含混不清:
awk: syntax error near line 2
awk: bailing out near line 2
您可能会花几小时的时间查看第 2 行,试图找出它为什么阻碍程序运行;这就是支持使用源文件的一个有力论据。
切记有两条规则可以帮助您避免出现语法错误:
1. 确保命令位于括号中,而括号位于单引号中。没有使用这些字符之一必然导致程序无法运行。
2. 搜索命令需要位于斜线之间。要找出住在印第安那州的员工,您必须使用“/IN/”而不是“IN”。
结论
尽管 AWK 完全代表另外的含意,但它应该是管理员智能工具包的首字母缩写。连同 SED 一起,AWK 实用工具是 Linux 管理员所拥有的功能最强大和灵活的
工具之一。通过了解其语言的一些特性,您可以开辟出能够简化任务的领域,否则这些任务将会是非常费时和困难的。
AWK:Linux 管理员的智能工具包
AWK 实用工具带有其自己的自包含语言,它不仅是 Linux 中也是任何环境中现有的功能最强大的数据处理引擎之一。这种编程及数据操作语言(其名称得自于它的创始人 Alfred Aho、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母)的最大功能取决于一个人所拥有的知识。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
AWK 是什么?
最简单地说,AWK 是一种用于处理文本的编程语言工具。AWK 实用工具的语言在很多方面类似于 shell 编程语言,尽管 AWK 具有完全属于其本身的语法。在最初创造 AWK 时,其目的是用于文本处理,并且这种语言的基础是,只要在输入数据中有模式匹配,就执行一系列指令。该实用工具扫描文件中的每一行,查找与命令行中所给定内容相匹配的模式。如果发现匹配内容,则进行下一个编程步骤。如果找不到匹配内容,则继续处理下一行。
尽管操作可能会很复杂,但命令的语法始终是:
awk '{pattern + action}' {filenames}
其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号 ({}) 不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。
了解字段
实用工具将每个输入行分为记录和字段。记录是单行的输入,而每条记录包含若干字段。默认的字段分隔符是空格或制表符,而记录的分隔符是换行。虽然在默认情况下将制表符和空格都看作字段分隔符(多个空格仍然作为一个分隔符),但是可以将分隔符从空格改为任何其它字符。
为了进行演示,请查看以下保存为 emp_names 的员工列表文件:
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
当 AWK 读取输入内容时,整条记录被分配给变量 $0。每个字段以字段分隔符分开,被分配给变量 $1、$2、$3 等等。一行在本质上可以包含无数个字段,通过字段号来访问每个字段。因此,命令
awk '{print $1,$2,$3,$4,$5}' names
将会产生的打印输出是
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
值得注意的一项重要内容是,AWK 解释由空格分隔的五个字段,但当它打印显示内容时,在每个字段间只有一个空格。利用为每个字段指定了唯一号码的功能,您可以选择只打印特定的字段。例如,只打印每条记录的姓名时,只需选择第二个和第三个字段进行打印:
$ awk '{print $2,$3}' emp_names
DULANEY EVAN
DURHAM JEFF
STEEN BILL
FELDMAN EVAN
SWIM STEVE
BOGUE ROBERT
JUNE MICAH
KANE SHERYL
WOOD WILLIAM
FERGUS SARAH
BUCK SARAH
TUTTLE BOB
$
您还可以指定按任何顺序打印字段,而无论它们在记录中是如何存在的。因此,只需要显示姓名字段,并且使其顺序颠倒,先显示名字再显示姓氏:
$ awk '{print $3,$2}' emp_names
EVAN DULANEY
JEFF DURHAM
BILL STEEN
EVAN FELDMAN
STEVE SWIM
ROBERT BOGUE
MICAH JUNE
SHERYL KANE
WILLIAM WOOD
SARAH FERGUS
SARAH BUCK
BOB TUTTLE
$
使用模式
通过包含一个必须匹配的模式,您可以选择只对特定的记录而不是所有的记录进行操作。模式匹配的最简单形式是搜索,其中要匹配的项目被包含在斜线 (/pattern/) 中。例如,只对那些居住在阿拉巴马州的员工执行前面的操作:
$ awk '/AL/ {print $3,$2}' emp_names
EVAN DULANEY
JEFF DURHAM
BILL STEEN
EVAN FELDMAN
STEVE SWIM
$
如果您不指定要打印的字段,则会打印整个匹配的条目:
$ awk '/AL/' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
$
对同一数据集的多个命令可以用分号 (;) 分隔开。例如,在一行中打印姓名,而在另一行中打印城市和州名:
$ awk '/AL/ {print $3,$2 ; print $4,$5}' emp_names
EVAN DULANEY
MOBILE AL
JEFF DURHAM
MOBILE AL
BILL STEEN
MOBILE AL
EVAN FELDMAN
MOBILE AL
STEVE SWIM
UNKNOWN AL
$
如果没有使用分号 (print $3,$2,$4,$5),则会在同一行中显示所有内容。另一方面,如果分别给出两个打印语句,则会产生完全不同的结果:
$ awk '/AL/ {print $3,$2} {print $4,$5}' emp_names
EVAN DULANEY
MOBILE AL
JEFF DURHAM
MOBILE AL
BILL STEEN
MOBILE AL
EVAN FELDMAN
MOBILE AL
STEVE SWIM
UNKNOWN AL
PHOENIX AZ
PHOENIX AZ
UNKNOWN AR
MUNCIE IN
MUNCIE IN
MUNCIE IN
MUNCIE IN
$
只有在列表中找到 AL 时才会给出字段三和字段二。但是,字段四和字段五是无条件的,始终打印它们。只有第一组花括号中的命令对前面紧邻的命令 (/AL/) 起作用。
结果非常不便于阅读,可以使其稍微更清晰一些。首先,在城市与州之间插入一个空格和逗号。然后,在每两行显示之后放置一个空行:
$ awk '/AL/ {print $3,$2 ; print $4", "$5"
"}' emp_names
EVAN DULANEY
MOBILE, AL
JEFF DURHAM
MOBILE, AL
BILL STEEN
MOBILE, AL
EVAN FELDMAN
MOBILE, AL
STEVE SWIM
UNKNOWN, AL
$
在第四和第五个字段之间,添加一个逗号和一个空格(在引号之间),在第五个字段后面,打印一个换行符 (
)。在 AWK 打印语句中还可以使用那些可在 echo 命令中使用的所有特殊字符,包括:
(换行)
(制表)
(退格)
f(进纸)
(回车)
因此,要读取全部五个最初由制表符分隔开的字段,并且也利用制表符打印它们,您可以编程如下
$ awk '{print $1" "$2" "$3" "$4" "$5}' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
$
通过连续设置多项标准并用管道 (|) 符号将其分隔开,您可以一次搜索多个模式匹配:
$ awk '/AL|IN/' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
$
这样可找到每个阿拉巴马州和印第安那州居民的匹配记录。但是在试图找出居住在亚利桑那州的人时,出现了一个问题:
$ awk '/AR/' emp_names
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AZ
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
$
员工 46026 和 46027 没有住在亚利桑那州;但是他们的名字中包含所搜索的字符序列。切记,当在 AWK 中进行模式匹配时,例如 grep、sed 或者大部分其他 Linux/Unix 命令,将在记录(行)中的任何位置查找匹配,除非指定进行其他操作。为解决这一问题,必须将搜索与特定字段联系起来。通过利用代字号 (?) 以及对特定字段的说明,可以达到这一目的,如下例所示:
$ awk '$5 ? /AR/' emp_names
46019 BOGUE ROBERT PHOENIX AZ
46021 JUNE MICAH PHOENIX AZ
46022 KANE SHERYL UNKNOWN AZ
$
代字号(表示匹配)的对应符号是一个前面带有感叹号的代字号 (!?)。这些字符通知程序,如果搜索序列没有出现在指定字段中,则找出与搜索序列相匹配的所有行:
$ awk '$5 !? /AR/' names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
46017 FELDMAN EVAN MOBILE AL
46018 SWIM STEVE UNKNOWN AL
46024 WOOD WILLIAM MUNCIE IN
46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN
46029 TUTTLE BOB MUNCIE IN
$
在这种情况下,将显示第五个字段中没有 AR 的所有行 — 包括两个 Sarah 条目,这两个条目确实包含 AR,但却是在第三个字段而不是第五个字段中。
花括号和字段分隔符
括号字符在 AWK 命令中起着很重要的作用。出现在括号之间的操作指出将要发生什么以及何时发生。当只使用一对括号时:
{print $3,$2}
括号间的所有操作同时发生。当使用多于一对的括号时:
{print $3}{print $2}
执行第一组命令,在该命令完成后执行第二组命令。注意以下两列清单的区别:
$ awk '{print $3,$2}' names
EVAN DULANEY
JEFF DURHAM
BILL STEEN
EVAN FELDMAN
STEVE SWIM
ROBERT BOGUE
MICAH JUNE
SHERYL KANE
WILLIAM WOOD
SARAH FERGUS
SARAH BUCK
BOB TUTTLE
$
$ awk '{print $3}{print $2}' names
EVAN
DULANEY
JEFF
DURHAM
BILL
STEEN
EVAN
FELDMAN
STEVE
SWIM
ROBERT
BOGUE
MICAH
JUNE
SHERYL
KANE
WILLIAM
WOOD
SARAH
FERGUS
SARAH
BUCK
BOB
TUTTLE
$
要利用多组括号进行重复查找,执行第一组中的命令直到完成为止;然后处理第二组命令。如果有第三组命令,则在第二组命令完成后执行它,以此类推。在所生成的打印输出中,有两个分隔的打印命令,因此先执行第一个命令,随后执行第二个命令,这样导致每个条目显示在两行而不是一行中。
区分两个字段的字段分隔符不一定始终是空格;它可以是任何可识别的字符。为进行演示,假定 emp_names 文件利用冒号而不是制表符来分隔字段:
$ cat emp_names
46012:DULANEY:EVAN:MOBILE:AL
46013:DURHAM:JEFF:MOBILE:AL
46015:STEEN:BILL:MOBILE:AL
46017:FELDMAN:EVAN:MOBILE:AL
46018:SWIM:STEVE:UNKNOWN:AL
46019:BOGUE:ROBERT:PHOENIX:AZ
46021:JUNE:MICAH:PHOENIX:AZ
46022:KANE:SHERYL:UNKNOWN:AR
46024:WOOD:WILLIAM:MUNCIE:IN
46026:FERGUS:SARAH:MUNCIE:IN
46027:BUCK:SARAH:MUNCIE:IN
46029:TUTTLE:BOB:MUNCIE:IN
$
如果试图通过指定所需要的第二个字段来打印姓氏
$ awk '{print $2}' emp_names
您最后会得到十二个空行。因为文件中没有空格,除了第一个字段之外没有可认别的字段。为解决这一问题,必须通知 AWK 是空格之外的另一个字符作为分隔符,有两种方法可通知 AWK 使用新的字段分隔符:使用命令行参数 -F,或在程序中指定变量 FS。两种方法的效果相同,只有一种例外情况,如下例所示:
$ awk '{FS=":"}{print $2}' emp_names
DURHAM
STEEN
FELDMAN
SWIM
BOGUE
JUNE
KANE
WOOD
FERGUS
BUCK
TUTTLE
$
$ awk -F: '{print $2}' emp_names
DULANEY
DURHAM
STEEN
FELDMAN
SWIM
BOGUE
JUNE
KANE
WOOD
FERGUS
BUCK
TUTTLE
$
在第一个命令中,头一条记录返回不正确的空行,而其他结果正确。直到读取第二条记录时,才识别字段分隔符并正确地执行。通过使用 BEGIN 语句可以纠正这一缺点(在后文详述)。-F 的功能非常类似于 BEGIN,能够正确地读取第一条记录并按要求执行。
在本文开始处我曾提到,默认的显示/输出字段分隔符是空格。通过使用输出字段分隔符 (OFS) 变量,可以在程序中更改此特性。例如,要读取文件(由冒号分隔)并以短划线显示,则命令是
$ awk -F":" '{OFS="-"}{print $1,$2,$3,$4,$5}' emp_names
46012-DULANEY-EVAN-MOBILE-AL
46013-DURHAM-JEFF-MOBILE-AL
46015-STEEN-BILL-MOBILE-AL
46017-FELDMAN-EVAN-MOBILE-AL
46018-SWIM-STEVE-UNKNOWN-AL
46019-BOGUE-ROBERT-PHOENIX-AZ
46021-JUNE-MICAH-PHOENIX-AZ
46022-KANE-SHERYL-UNKNOWN-AR
46024-WOOD-WILLIAM-MUNCIE-IN
46026-FERGUS-SARAH-MUNCIE-IN
46027-BUCK-SARAH-MUNCIE-IN
46029-TUTTLE-BOB-MUNCIE-IN
$
FS 和 OFS 是(输入)字段分隔符和输出字段分隔符,它们只是一对可以在 AWK 实用工具中使用的变量。例如,要在打印时为每行编号,可以采用以下方式使用 NR 变量:
$ awk -F":" '{print NR,$1,$2,$3}' emp_names
1 46012 DULANEY EVAN
2 46013 DURHAM JEFF
3 46015 STEEN BILL
4 46017 FELDMAN EVAN
5 46018 SWIM STEVE
6 46019 BOGUE ROBERT
7 46021 JUNE MICAH
8 46022 KANE SHERYL
9 46024 WOOD WILLIAM
10 46026 FERGUS SARAH
11 46027 BUCK SARAH
12 46029 TUTTLE BOB
$
找出员工号码处于 46012 和 46015 之间的所有行:
$ awk -F":" '/4601[2-5]/' emp_names
46012 DULANEY EVAN MOBILE AL
46013 DURHAM JEFF MOBILE AL
46015 STEEN BILL MOBILE AL
$
添加文本
可以按照添加控制序列或其他字符的相同方式将文本添加到显示中。例如,要将分隔符从空格改为冒号,则命令是
awk '{print $1":"$2":"$3":"$4":"$5}' emp_names > new_emp_names
在这种情况下,字符 (:) 包含在引号 ("/") 中,它被添加到每个字段之间。在引号之间的值可以是任何内容。例如,创建一个关于居住在阿拉巴马州的员工的外观类似数据库的显示:
$ awk '$5 ~ /AL/ {print "NAME: "$2", "$3"
CITY-STATE:
"$4", "$5"
"}' emp_names
NAME: DULANEY, EVAN
CITY-STATE: MOBILE, AL
NAME: DURHAM, JEFF
CITY-STATE: MOBILE, AL
NAME: STEEN, BILL
CITY-STATE: MOBILE, AL
NAME: FELDMAN, EVAN
CITY-STATE: MOBILE, AL
NAME: SWIM, STEVE
CITY-STATE: UNKNOWN, AL
$
数学操作
AWK 除了提供文本功能,还提供全部范围的算术操作符,包括以下符号:
+ 将数字相加
- 减
* 乘
/ 除
^ 执行指数运算
% 提供模
++ 将变量值加一
+= 将其他操作的结果分配给变量
— 将变量减一
-= 将减法操作的结果分配给变量
*= 分配乘法操作的结果
/= 分配除法操作的结果
%= 分配求模操作的结果
例如,假定您的机器上存在以下的文件,详细地列出硬件商店中的物品:
$ cat inventory
hammers 5 7.99
drills 2 29.99
punches 7 3.59
drifts 2 4.09
bits 55 1.19
saws 123 14.99
nails 800 .19
screws 80 .29
brads 100 .24
$
第一项业务定单是通过将第二个字段(数量)的值乘以第三个字段(价格)的值,计算每种物品的库存价值:
$ awk '{print $1,"QTY: "$2,"PRICE: "$3,"TOTAL: "$2*$3}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95
drills QTY: 2 PRICE: 29.99 TOTAL: 59.98
punches QTY: 7 PRICE: 3.59 TOTAL: 25.13
drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18
bits QTY: 55 PRICE: 1.19 TOTAL: 65.45
saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77
nails QTY: 800 PRICE: .19 TOTAL: 152
screws QTY: 80 PRICE: .29 TOTAL: 23.2
brads QTY: 100 PRICE: .24 TOTAL: 24
$
如果这些行本身并不重要,您只是希望确定商店中有多少件物品,则可以分配一个普通变量,按照每条记录中的物品数量增加:
$ awk '{x=x+$2} {print x}' inventory
5
7
14
16
71
194
994
1074
1174
$
根据这一数据,商店中有 1174 件物品。第一次执行时,变量 x 没有值,因此它采用第一行第二个字段的值。第二次执行时,它保留了第一行的值并加上第二行的值,以此类推,直到达到累计的总合。
可以应用相同的过程来确定现有库存的总价值:
$ awk '{x=x+($2*$3)} {print x}' inventory
39.95
99.93
125.06
133.24
198.69
2042.46
2194.46
2217.66
2241.66
$
因此,1174 件物品的价值是 $2,241.66。虽然这一过程可以获得总计值,但它的外观很差,需要加工成实际的报表。利用一些附加项,很容易使显示变得更整洁:
$ awk '{x=x+($2*$3)}{print $1,"QTY: "$2,"PRICE: "$3,"TOTAL: "$2*$3,"BAL: "x}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95 BAL: 39.95
drills QTY: 2 PRICE: 29.99 TOTAL: 59.98 BAL: 99.93
punches QTY: 7 PRICE: 3.59 TOTAL: 25.13 BAL: 125.06
drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18 BAL: 133.24
bits QTY: 55 PRICE: 1.19 TOTAL: 65.45 BAL: 198.69
saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77 BAL: 2042.46
nails QTY: 800 PRICE: .19 TOTAL: 152 BAL: 2194.46
screws QTY: 80 PRICE: .29 TOTAL: 23.2 BAL: 2217.66
brads QTY: 100 PRICE: .24 TOTAL: 24 BAL: 2241.66
$
该过程提供了每条记录的清单,同时将总价值分配给库存值,并保持商店资产的运作平衡。
BEGIN 和 END
使用 BEGIN 和 END 语句可以分别指定在处理实际开始之前或者完成之后进行操作。BEGIN 语句最常用于建立变量或显示标题。另一方面,END 语句可用于在程序结束后继续进行处理。
在前面的示例中,利用以下例程生成了物品的总价值:
awk '{x=x+($2*$3)} {print x}' inventory
该例程在运行总计累加时显示了文件中的每一行。没有其他方法可以指定它,而不让在每一行进行打印也导致它始终不打印出来。但是,利用 END 语句可以避免这一问题:
$ awk '{x=x+($2*$3)} END {print "Total Value of Inventory:"x}' inventory
Total Value of Inventory: 2241.66
$
定义了变量 x,它对每一行进行处理;但是,在所有处理完成之前不会生成显示。尽管可以作为独立例程使用,它也可以置入到先前的代码列表,添加更多信息并生成更完整的报表:
$ awk '{x=x+($2*$3)} {print $1,"QTY: "$2,"PRICE:
"$3,"TOTAL: "$2*$3} END {print "Total Value of Inventory: " x}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95
drills QTY: 2 PRICE: 29.99 TOTAL: 59.98
punches QTY: 7 PRICE: 3.59 TOTAL: 25.13
drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18
bits QTY: 55 PRICE: 1.19 TOTAL: 65.45
saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77
nails QTY: 800 PRICE: .19 TOTAL: 152
screws QTY: 80 PRICE: .29 TOTAL: 23.2
brads QTY: 100 PRICE: .24 TOTAL: 24
Total Value of Inventory: 2241.66
$
BEGIN 命令与 END 的工作方式相同,但它建立了那些需要在完成其他工作之前所做的项目。该过程最常见的目的是创建报表的标题。此例程的语法类似于
$ awk 'BEGIN {print "ITEM QUANTITY PRICE TOTAL"}'
输入、输出和源文件
AWK 工具可以从文件中读取其输入,正如在此之前所有示例所做的那样,它也可以从其他命令的输出中获取输入。例如:
$ sort emp_names | awk '{print $3,$2}'
awk 命令的输入是排序操作的输出。除了 sort,还可以使用任何其他的 Linux 命令 — 例如 grep。该过程允许您在离开所选定字段前对文件执行其他操作。
类似于解释程序,AWK 使用输出改向操作符 > 和 >> 将其输出放入文件中而不是标准输出设备。这些符号的作用类似于它们在解释程序中的对应符号,因此 > 在不存在文件时创建文件,而 >> 追加到现有文件的尾部。请看以下的示例:
$ awk '{print NR, $1 ) > "/tmp/filez" }' emp_names
$ cat /tmp/filez
1 46012
2 46013
3 46015
4 46017
5 46018
6 46019
7 46021
8 46022
9 46024
10 46026
11 46027
12 46029
$
检查该语句的语法,您会看到输出改向是在打印语句完成后进行的。必须将文件名包含在引号中,否则它只是一个未初始化的 AWK 变量,而将指令联接起来会在 AWK 中产生错误。(如果不正确地使用改向符号,则 AWK 无法了解该符号意味着“改向”还是一个关系操作符。)
在 AWK 中输出到管道也类似于解释程序中所实现的相同操作。要将打印命令的输出发送到管道中,可以在打印命令后附加管道符号以及命令的名称,如下所示:
$ awk '{ print $2 | "sort" }' emp_names
BOGUE
BUCK
DULANEY
DURHAM
FELDMAN
FERGUS
JUNE
KANE
STEEN
SWIM
TUTTLE
WOOD
$
这是输出改向的情况,必须将命令包含在引号中,而管道的名称是被执行命令的名称。
AWK 所使用的命令可以来自两个地方。首先,可以在命令行中指定它们,如示例中所示。其次,它们可以由源文件提供。如果是这种情况,通过 -f 选项将这种情况向 AWK 发出警告。演示如下:
$ cat awklist
{print $3,$2}
{print $4,$5,"
"}
$
$ awk -f awklist emp_names
EVAN DULANEY
MOBILE AL
JEFF DURHAM
MOBILE AL
BILL STEEN
MOBILE AL
EVAN FELDMAN
MOBILE AL
STEVE SWIM
UNKNOWN AL
ROBERT BOGUE
PHOENIX AZ
MICAH JUNE
PHOENIX AZ
SHERYL KANE
UNKNOWN AR
WILLIAM WOOD
MUNCIE IN
SARAH FERGUS
MUNCIE IN
SARAH BUCK
MUNCIE IN
BOB TUTTLE
MUNCIE IN
$
注意,在源文件中的任何地方或者在命令行中调用它时,不使用单引号。单引号只用于区别命令行中的命令与文件名称。
如果简单的输出不能处理您的程序中所需要的复杂信息,则可以尝试由 printf 命令获得的更加复杂的输出,其语法是
printf( format, value, value ...)
该语法类似于 C 语言中的 printf 命令,而格式的规格是相同的。通过插入一项定义如何打印数值的规格,可以定义该格式。格式规格包含一个跟有字母的 %。类似于打印命令,printf 不必包含在圆括号中,但是可以认为使用圆括号是一种良好的习惯。
下表列出 printf 命令提供的各种规格。
规格 说明
%c 打印单个 ASCII 字符
%d 打印十进制数
%e 打印数字的科学计数表示
%f 打印浮点表示
%g 打印 %e 或 %f;两种方式都更简短
%o 打印无符号的八进制数
s 打印 ASCII 字符串
%x 打印无符号的十六进制数
%% 打印百分号;不执行转换
可以在 % 与字符之间提供某些附加的格式化参数。这些参数进一步改进数值的打印方式:
参数 说明
- 将字段中的表达式向左对齐
,width 根据需要将字段补齐到指定宽度(前导零使用零将字段补齐)
.prec 小数点右面数字的最大字符串宽度或最大数量
printf 命令能够控制并将数值从一种格式转换为另一种格式。当需要打印变量的值时,只需提供一种规格,指示 printf 如何打印信息(通常包含在双引号中)即可。必须为每个传递到 printf 的变量包含一个规格参数;如果包含过少的参数,则 printf 不会打印所有的数值。
处理错误
AWK 工具报告所发生错误的方式很令人恼火。一个错误会阻碍任何操作的进行,所提供的错误信息非常含混不清:
awk: syntax error near line 2
awk: bailing out near line 2
您可能会花几小时的时间查看第 2 行,试图找出它为什么阻碍程序运行;这就是支持使用源文件的一个有力论据。
切记有两条规则可以帮助您避免出现语法错误:
1. 确保命令位于括号中,而括号位于单引号中。没有使用这些字符之一必然导致程序无法运行。
2. 搜索命令需要位于斜线之间。要找出住在印第安那州的员工,您必须使用“/IN/”而不是“IN”。
结论
尽管 AWK 完全代表另外的含意,但它应该是管理员智能工具包的首字母缩写。连同 SED 一起,AWK 实用工具是 Linux 管理员所拥有的功能最强大和灵活的工具之一。通过了解其语言的一些特性,您可以开辟出能够简化任务的领域,否则这些任务将会是非常费时和困难的。