SED,AWK使用详解

发布时间 2023-12-27 16:52:13作者: Insomnia_ki_higher

SED,AWK使用

1.sed命令

sed使用方法

​ 一种叫命令模式,一种叫脚本模式

命令行模式:

格式

sed [option] 'sed的命令|地址定位'	filename
说明:引用shell script中的变量应使用双引号,而非平常使用的单引号
option:
-e:进行多项编辑,即对输入行应用多条sed命令时使用
-n:取消默认的输出
-f:指定sed脚本的文件名
-r:使用扩展正则表达式
-i inplace,原地编辑
ps:-i和-n不能一起使用,否则会清空原有文件
	-i和'p'不能一起使用

[root@jumpserver ~]# sed 'p' 1.txt 
#version=DEVEL
#version=DEVEL
10.40.166.3
10.40.166.3
# System authorization information
# System authorization information
auth --enableshadow --passalgo=sha512
auth --enableshadow --passalgo=sha512
# Use CDROM installation media
# Use CDROM installation media
当我们去打印的时候会发现,我们的每一行都打印了两次,因为第一行是将其放在缓冲区中所显示的,
面对这种情况我们可以选择-n取消默认的输出

常用命令和选项

p :打印行
d :删除行
i:在当前行之前插入文本
a:在当前行后添加一行或者多行
c:替换匹配到的这一行
ps:在vim编辑器中,在上一行插入文本为大写O,在下一行插入文本是小写o
#打印第一行
[root@jumpserver ~]# sed -n '1p' 1.txt 
#version=DEVEL
#删除第一行
[root@jumpserver ~]# sed -n '1d' 1.txt 
打印1-3行
[root@jumpserver ~]# sed -n '1,3p' 1.txt 
#version=DEVEL
10.40.166.3
# System authorization information

#在每一行的上一行插入hello
[root@jumpserver ~]# sed  'ihello' 1.txt 
hello
#version=DEVEL
hello
10.40.166.3
hello
# System authorization information
hello
auth --enableshadow --passalgo=sha512
hello
# Use CDROM installation media
#在第一行之前插入hello
[root@jumpserver ~]# sed '1ihello' 1.txt 
hello
 version=DEVEL
10.40.166.3
  System authorization information
auth --enableshadow --passalgo=sha512
  Use CDROM installation media
#在第一行之前插入两行hello
[root@jumpserver ~]# sed '1ihello\nhello' 1.txt 
hello
hello
 version=DEVEL
10.40.166.3
  System authorization information
auth --enableshadow --passalgo=sha512
  Use CDROM installation media
#在最后一行之后添加
[root@jumpserver ~]# sed '$ahello' 1.txt 
#用100.100.100.1替换第二行
[root@jumpserver ~]# sed '2c100.100.100.1' 1.txt 	
 version=DEVEL
100.100.100.1
  System authorization information
auth --enableshadow --passalgo=sha512
  Use CDROM installation media
#把包含有version关键字的那一行替换成helloworld
[root@jumpserver ~]# sed '/version/chello world' 1.txt 
[root@jumpserver ~]# sed '/[a-z]/chelloworld' 1.txt 
helloworld
10.40.166.3
helloworld
helloworld
helloworld
r	从文件中读取输入行
w	将所选的行写入文件
#	将文件/etc/hosts文件放在当前1.txt文件下读取
[root@jumpserver ~]# sed '3r /etc/hosts' 1.txt 
 version=DEVEL
10.40.166.3
  System authorization information
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
auth --enableshadow --passalgo=sha512
  Use CDROM installation media
[root@jumpserver ~]#  sed '1,3w ./a.txt' 1.txt
ps:sed -r 就可以实现扩展正则了
#将1.txt文件里面有四个连续数字的那一行写入a.txt文件
[root@jumpserver ~]# sed '/[0-9]\{4\}/w a.txt' 1.txt
! 对所选行以外的所有行应用命令,放到行数之后
过滤有ip地址的那一行
[root@jumpserver ~]# sed -rn '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' 1.txt 
s	用一个字符串替换另一个
g	在行内进行全局替换
类似与vi编辑器中s和g的用法
#使用VERSION替换version
 [root@jumpserver ~]# sed -n 's/version/VERSION/p' 1.txt 
 VERSION=DEVEL
ps:分隔符不一定要使用/。可以选择#或者其他符号
#在1-5行行首+#号
[root@jumpserver ~]# sed -n '1,5s/^/#/gp' 1.txt 
#删除文件里面的/:.
sed -n 's/[/:n]//gp' 1.txt
&:保存查找串以便在替换串中引用 \(\) 它能保存你前面所查找的串,在替换时候,若你需要使用,直接使用&就可以代替它。
例如:
[root@jumpserver ~]# sed -n 's/version/& 1.1/gp' 1.txt 

[root@ecs-shell ~]# cat shadow 
root:$6$MisAYPlR$txZjLIpoiTq6.B5x.JrIdkxdPwyiuLfeqU77RKwSl5jtkEUfN/oYvvGhIZ9pbas1YLlpLp1m4B0aWkYNZ.8nc0:19702:0:99999:7:::
#这两者相似:两者不同之处在于,\(\)可以用作选择性保留。
[root@ecs-shell ~]# sed -n 's/root/&adm/gp' shadow
rootadm:$6$MisAYPlR$txZjLIpoiTq6.B5x.JrIdkxdPwyiuLfeqU77RKwSl5jtkEUfN/oYvvGhIZ9pbas1YLlpLp1m4B0aWkYNZ.8nc0:19702:0:99999:7:::
[root@ecs-shell ~]# sed -n 's/\(root\)/\1adm/gp' shadow
rootadm:$6$MisAYPlR$txZjLIpoiTq6.B5x.JrIdkxdPwyiuLfeqU77RKwSl5jtkEUfN/oYvvGhIZ9pbas1YLlpLp1m4B0aWkYNZ.8nc0:19702:0:99999:7:::
#将含有root的那一行的^root前加上#
[root@ecs-shell ~]# sed -nr '/root/s/^root/#&/pg' /etc/passwd
#root:x:0:0:root:/root:/bin/bash


=:打印行号
#获取文件以bash结尾的行号
[root@jumpserver ~]# sed -n '/bash$/=' /etc/passwd
1
22
23
[root@ecs-shell ~]# sed -n '/:$/=' shadow
1

-e:多次编辑的含义
[root@ecs-shell ~]# sed -ne '/bash$/=' -ne '/bash$/p' /etc/passwd
1
root:x:0:0:root:/root:/bin/bash
22
stu1:x:1000:1000::/home/stu1:/bin/bash
23
jerry:x:1001:1001::/home/jerry:/bin/bash

-i:直接对原文件进行修改 慎用!
-i一般不能和-n一起用,不能使用p
[root@ecs-shell ~]# sed -i 's/root/ROOT/g' passwd
[root@ecs-shell ~]# cat passwd
ROOT:x:0:0:ROOT:/ROOT:/bin/bash

过滤掉文件当中的空行和注释行

# grep -v '^#' /etc/vsftpd/vsftpd.conf | grep -v '^$'  or
# grep -E -v '^#|^$' /etc/vsftpd/vsftpd.conf  or
# sed -ner '/^#|^$/' /etc/vsftpd/vsftpd.conf
# sed -e '/^#/d' -e'/^$/d' /etc/vsftpd/vsftpd.conf
# sed '/^#/d;/^$/d' /etc/vsftpd/vsftpd.confF

过滤掉smb.conf中的空行,注释等

[root@jumpserver ~]# sed -r '/(^#|^$|^:|\t)/d' smb.conf 

过滤出/etc/sysconfig/network-script/ifcfg-ens33里面的ip地址,子网掩码和dns服务器地址。

[root@jumpserver ~]# sed -n -r  '/^(GATEWAY|DNS1|NETMASK)/p' /etc/sysconfig/network-scripts/ifcfg-ens33 |sed -n -r 's/([A-Z]|=)//pg' 
192.168.208.2
1192.168.208.2
255.255.255.0
从以下结果中过滤出ip地址,dns服务器地址,和子网掩码地址
[root@jumpserver ~]# ifconfig ens33
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.208.17  netmask 255.255.255.0  broadcast 192.168.208.255
        inet6 fe80::651d:b792:addb:5e53  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:51:c9:5c  txqueuelen 1000  (Ethernet)
        RX packets 35126  bytes 17623506 (16.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 17956  bytes 1607125 (1.5 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@jumpserver ~]# ifconfig ens33 |sed -n '2p' |sed -n -r 's/[a-z]//pg'|sed -n 's/ /\n/pg'|sed  '/^$/d' 
192.168.208.17
255.255.255.0
192.168.208.255

sed脚本

sed脚本的执行方式

#使用脚本处理文件
sed -f scripts.sed file
,/sed.sh file
脚本第一行写
#!/bin/sed -f
1,5d
s/root/ROOT
3i777
5i888
/root/p

1)脚本文件是一个sed的清单
2)在每行的末尾不能有任何空格,制表符或其他文本
3)如果在一行中有多个命令,应该用分号隔开
4)不需要不可用引号保护命令
5)#号开头的为注释

#!/bin/bash
ip=$(sed -n '/IPADDR/p' /etc/sysconfig/network-scripts/ifcfg-ens33|sed -n -r 's/([A-Z]|=)//pg')
ip1=$(cut -d. -f4 $ip)
name=server$ip1.itcast.cc
echo $name > /etc/hostname
#自动配置可用的yum源
[ -f /etc/yum.repos.d/CentOS-Base.repo.bak1 ]&&rm -rf /etc/yum.repos.d/CentOS-Base.repo.bak1 || mv CentOS-Base.repo CentOS-Base.repo.bak1
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
if [ $? -eq 0 ];then
yum clean all
yum makecache
fi
#自动关闭防火墙和selinux
systemctl stop firewalld
setenforce 0

2.awk

awk命令模式语法

[root@server ~]# awk 选项 'command' 文件名
常用选项:
F:定义字段分隔符,默认的分隔符为空格
v:定义变量并赋值

command部分

正则表达式,地址定位

#打印含root那一行的
cat passwd | awk '/root/{awk语句}' 
#打印1-5行
cat passwd | awk -F":" 'NR==1,NR==5{print $0}'

'/^root/,/^ftp/{awk语句}':/(^root|^ftp)/p

{awk语句1;awk语句2;...}

'{print $0,print $1}'	sed中:'p'
'NR==5{Print $0}'	sed中:'5p'
ps:awk语句之间用;隔开

BEGIN END

'BEGIN{awk语句};{处理中};END{awk语句}'
'BEGIN{awk语句};{处理中}'
'{处理中};END{awk语句}'

脚本模式

​ 脚本执行

方法一:
awk 选项 -f awk脚本文件 要处理的文本文件
awk -f awk.sh a.txt
sed -f sed.sh -i a.txt sed修改源文件
方法二:
./awk的脚本文件(或脚本文件)	要处理的文本文件
./awk.sh	filename
./sed.sh	filename

脚本的编写

#!/bin/awk -f	定义魔法字符
以下awk的命令清单,不要用引号保护命令,多个命令用分号分隔
BEGIN{FS=":"}
NR==1,NR==3{print $1"\t"$NF}
...

打印文件的第一列,和最后两列

[root@server ~]# awk -F: '{ print $1,$(NF-1),$NF }' 1.txt 
#打印匹配root关键字的那一行
[root@server ~]# awk '/^root/{print $0}' 1.txt 
root:x:0:0:root:/root:/bin/bash
#打印前五行
[root@server ~]# awk 'NR==1,NR==5{print $0 }' 1.txt 
[root@server ~]# awk 'NR==1,NR==5' 1.txt 
#打印前五行中以root关键字开头或者以adm开头的
[root@server ~]# awk 'NR==1,NR==5' 1.txt | awk '/(^adm|^root)/{print $0}' 1.txt 
[root@server ~]# awk 'NR==1,NR==5' 1.txt | awk '/(^adm|^root)/{print $0}' 1.txt  
#打印从root开头的和adm的行
[root@server ~]# awk '/^root/,/^lp/{print $0}' 1.txt 
[root@server ~]# awk -F: '/^root/,/^lp/{print $1,$NF}' 1.txt 
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
我们会发现每一列都默认为空格隔开,所以我们可以选择"我们选择的分隔符"
[root@server ~]# awk -F: '/^root/,/^lp/{print $1"的bash为"$NF}' 1.txt 
root的bash为/bin/bash
bin的bash为/sbin/nologin
daemon的bash为/sbin/nologin
adm的bash为/sbin/nologin
lp的bash为/sbin/nologin
除了以上-F定义间隔符的方法我们也可以选择使用BEGIN去定义
[root@server ~]# awk 'BEGIN{FS=":"}; /^root/,/^adm/{print $1,$NF}' 1.txt 
#在begin中定义输出字段分隔符
[root@server ~]# awk -F: 'BEGIN{OFS="\t\t"};/root/{print $1,$NF}' 1.txt 
RS: 判断awk在读取配置文件时,如何判断是否为一行,例如我们在BEGIN中指定RS="\t"
则每当我们遇到"\t"就认为它换行了.我们就可以对新的文件进行操作 
ORS:当给此参数值时,我们在输出我们的文本时,会以我们所设置的换行符对每一行文字进行分割。
例如:
[root@server ~]# awk -F: '{ORS="\t\t"};{print $1,$NF}' 1.txt 
root /bin/bash		bin /sbin/nologin		daemon /sbin/nologin	adm /sbin/nologin		lp /sbin/nologin		sync /bin/sync		shutdown /sbin/shutdown	halt /sbin/halt		mail /sbin/nologin		operator /sbin/nologin		
NF

awk定义变量

awk中定义的变量不需要$
在大括号里{print $num}和{print num}前一个是打印第num行,后一个是打印num
#打印第一列
[root@server ~]# awk -v num=1 -F: '{print $num}' 1.txt 
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
operator
#文件处理之前打印num这个变量
[root@server100 awk]# awk -v num=1 'BEGIN{print num}'
1
#打印num变量
[root@server ~]# awk -v num=1 -F: '{print num}' 1.txt 
1
1
1
1
1
1
1
1
1
1

awk中BEGIN..END使用

1.BEGIN:表示在程序开始前执行

2.END:表示所有文件处理后执行

3用法:‘BEGIN{};{执行中};END{}’

练习:

[root@server ~]# awk -F: 'BEGIN{print "login_shell\t\tlogin_home\n******************"};{print $NF"\t\t"$(NF-1)};END{print "*****************"}' 1.txt 
login_shell		login_home
******************
/bin/bash		/root
/sbin/nologin		/bin
/sbin/nologin		/sbin
/sbin/nologin		/var/adm
/sbin/nologin		/var/spool/lpd
/bin/sync		/sbin
/sbin/shutdown		/sbin
/sbin/halt		/sbin
/sbin/nologin		/var/spool/mail
/sbin/nologin		/root
*****************



[root@server100 awk]# cat passwd | awk -F: 'BEGIN{OFS="\t\t";print "login_shell\t\tlogin_home\n***************************************"};{print $NF,$(NF-1)};END{print "*****************************************"} '
login_shell		login_home
***************************************
/bin/bash		/root
/sbin/nologin		/bin
/sbin/nologin		/sbin
/sbin/nologin		/var/adm
/sbin/nologin		/var/spool/lpd
/bin/sync		/sbin
/sbin/shutdown		/sbin
/sbin/halt		/sbin
/sbin/nologin		/var/spool/mail
/sbin/nologin		/ROOT
/sbin/nologin		/usr/games
/sbin/nologin		/var/ftp
/sbin/nologin		/
/sbin/nologin		/
/sbin/nologin		/
/sbin/nologin		/
/sbin/nologin		/var/spool/postfix
/sbin/nologin		/var/empty/sshd
/sbin/nologin		/var/lib/chrony
/sbin/nologin		/
/sbin/nologin		/dev/null
/bin/bash		/home/tune
*****************************************

[root@server ~]# awk -F: 'BEGIN{print "u_name\t\th_dir\t\tshell\n******************************************************************"};{print$1"\t\t"$6"\t\t"$NF};END{print "******************************************************************"}' /etc/passwd
我们会发现这样并不美观
我们需要对其进行改动

awk的格式化输出

printf函数 类似于echo -n
#awk -F: '{printf "%-15s %-10s %-15s\n",$1,$2,$3}' /etc/passwd
%-15s:
%s:为字符输出
-:左对齐
15:字符长度
[root@server ~]# awk -F: 'BEGIN{print "u_name\t\th_dir\t\tshell\n******************************************************************"};{printf "%-15s %-20s %-15s\n", $1,$6,$NF};END{print "******************************************************************"}' /etc/passwd

#使用print(原来的样子)
[root@server100 awk]# cat passwd | awk -F: 'BEGIN{OFS="\t\t";print "u_name\t\t_dir\t\tshell\n********************************************************"};{print $1,$(NF-1),$(NF)};END{print "********************************************************"}'
u_name		_dir		shell
********************************************************
root		/root		/bin/bash
bin		/bin		/sbin/nologin
daemon		/sbin		/sbin/nologin
adm		/var/adm		/sbin/nologin
lp		/var/spool/lpd		/sbin/nologin
sync		/sbin		/bin/sync
shutdown		/sbin		/sbin/shutdown
halt		/sbin		/sbin/halt
mail		/var/spool/mail		/sbin/nologin
operator		/ROOT		/sbin/nologin
games		/usr/games		/sbin/nologin
ftp		/var/ftp		/sbin/nologin
nobody		/		/sbin/nologin
systemd-network		/		/sbin/nologin
dbus		/		/sbin/nologin
polkitd		/		/sbin/nologin
postfix		/var/spool/postfix		/sbin/nologin
sshd		/var/empty/sshd		/sbin/nologin
chrony		/var/lib/chrony		/sbin/nologin
tcpdump		/		/sbin/nologin
tss		/dev/null		/sbin/nologin
tune		/home/tune		/bin/bash
********************************************************
#使用printf
[root@server100 awk]# cat passwd | awk -F: 'BEGIN{OFS="\t\t";print "u_name\t\t_dir\t\tshell\n********************************************************"};{printf "%-15s %-15s %-15s\n",$1,$(NF-1),$(NF)};END{print "********************************************************"}'
u_name		_dir		shell
********************************************************
root            /root           /bin/bash      
bin             /bin            /sbin/nologin  
daemon          /sbin           /sbin/nologin  
adm             /var/adm        /sbin/nologin  
lp              /var/spool/lpd  /sbin/nologin  
sync            /sbin           /bin/sync      
shutdown        /sbin           /sbin/shutdown 
halt            /sbin           /sbin/halt     
mail            /var/spool/mail /sbin/nologin  
operator        /ROOT           /sbin/nologin  
games           /usr/games      /sbin/nologin  
ftp             /var/ftp        /sbin/nologin  
nobody          /               /sbin/nologin  
systemd-network /               /sbin/nologin  
dbus            /               /sbin/nologin  
polkitd         /               /sbin/nologin  
postfix         /var/spool/postfix /sbin/nologin  
sshd            /var/empty/sshd /sbin/nologin  
chrony          /var/lib/chrony /sbin/nologin  
tcpdump         /               /sbin/nologin  
tss             /dev/null       /sbin/nologin  
tune            /home/tune      /bin/bash      
********************************************************

​ 经过格式化输出,我们会发现此时显示较之前美观

awk与正则表达式的混合使用

#从第一行开始匹配到lp开头的行
[root@server ~]# awk 'NR==1,/lp/{print $0}' 1.txt 
#打印从第五行到第六行
[root@server ~]# awk 'NR==5,NR==6' 1.txt 
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
[root@server ~]# awk 'NR>=5&&NR<=6' 1.txt 
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
#打印第五行或第六行
[root@server ~]# awk 'NR==5||NR==6' 1.txt 
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
打印以root开头或者以lp开头的行
[root@server ~]# grep -E '(^root|^lp)' 1.txt 
[root@server ~]# sed -n -r '/(^root|^lp)/p' 1.txt 
root:x:0:0:root:/root:/bin/bash
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
[root@server ~]# awk '/^root/ || /^lp/ {print $0}' 1.txt 
或者
[root@server ~]# awk '/^root|^lp/ {print $0}' 1.txt 
即在正则表达式中一个|为或逻辑表达式中两个||为或

练习:从ifconfig里面提取ip地址

[root@server ~]# ifconfig  ens33
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.208.17  netmask 255.255.255.0  broadcast 192.168.208.255
        inet6 fe80::651d:b792:addb:5e53  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:51:c9:5c  txqueuelen 1000  (Ethernet)
        RX packets 21279  bytes 1642627 (1.5 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10928  bytes 1149666 (1.0 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
[root@ecs-shell ~]# ifconfig | awk 'NR==2' 
        inet 192.168.1.173  netmask 255.255.255.0  broadcast 192.168.1.255
[root@ecs-shell ~]# ifconfig | awk 'NR==2'| awk -F"[ :]+" 'BEGIN{OFS="\n"};{print $3,$5,$7}'
192.168.1.173
255.255.255.0
192.168.1.255

awk流程控制

if语句

类似与linux的shell:
if语句:
if [xxx];then
xxx
fi

格式
awk [选项] '正则,地址定位{awk语句}' 文件名
{if(表达式){语句1;语句2;...}
练习:
[root@server ~]#  awk -F: '{if($3>=1000&&$3<=65535){print $1, $3}}' /etc/passwd
[root@server ~]# awk -F: '{if( $7=="/bin/bash" ){print $1"是用户自己创建的"} else {print $1"是系统创建的"}}' /etc/passwd
if语句
if[xxx];then
xxx
else if[xxx];then;
xxx
else
xxx
在awk当中 :{if(){}else if(){}else if(){} else{}}
例如:判断用户为自己创建的还是系统用户
[root@server ~]# awk -F: '{if ($3==0) {print $1"是管理员"}else if($3>=1&&$3<=1000){print $1"为系统用户"} else {print $1"为用户自己创建的"}}' /etc/passwd
计算各种用户的数量
[root@server ~]# awk -F: 'BEGIN{};{if ($3==0) {i++}else if($3>=1&&$3<=1000){j++} else {k++}};END{print"管理员用户的数量为"i; print "系统用户数量为"j;print"普通用户数量为"k}' /etc/passwd

awk循环

while:
awk 'BEGIN{i=1;while(i<=5){print i i++ }}'
for:
[root@server ~]# awk 'BEGIN{for(i=1;i<=5;i++){print i}}'
1
2
3
4
5
[root@server ~]# awk 'BEGIN{i=1;while(i<=5){print i;i=i+2}}'
1
3
5
[root@server ~]# awk 'BEGIN{i=1;while(i<=10){print i;i=i+2}}'
#计算1-5的和
[root@server ~]# awk 'BEGIN{i=1;sum=0;while(i<=5){sum=sum+i;i++;}{print sum}}'
#计算1-10奇数的和
[root@server ~]# awk 'BEGIN{sum=0;for(i=1;i<=10;i+=2){sum=sum+i}{print sum}}'
#打印一个图形
[root@server ~]# awk 'BEGIN{for(i=1;i<=5;i++){for(j=1;j<=i;j++){printf j}print}}'

awk的算术运算

[root@server ~]# awk 'BEGIN{print 1*1}'

#代码
[root@server ~]# awk -F: '{shells[$NF]++};END{for (i in shells){print i,shells[i]}}' /etc/passwd
从关联数组中获取下标:for(i in shells)