Android性能测试

作者 Art_Collector (点击查看作者的简书主页)

原文 http://www.jianshu.com/p/f07f83d1fbdf

2016.08.26 18:56* 字数 2649 阅读 845评论 0

那些年我们用过的显示性能指标
Android客户端性能优化(魅族资深工程师毫无保留奉献)
这一次,我优化了37%的内存
Android性能测试之fps获取
Android应用性能测试之CPU和内存占用
android如何查看cpu的占用率和内存泄漏
如何解决CPU使用率过高问题
ADB Shell Commands
Android应用性能测试
强烈推荐转载-Android 性能测试(抱歉,没找到原文)
Android 性能测试实践(四) 流量

测试维度


  • CPU占用率
  • 内存使用
  • 响应时间/加载速度
  • 显示性能(FPS)
  • 电量、流量
  • Crash
  • ANR

命令介绍


本文涉及到的命令主要有:

  • > 表示输出内容写入到文件 eg. echo 121212 > 12.txt
  • >> 表示输出内容追加到文件 eg. echo 343434 >> 12.txt
  • Ctrl + c 快捷键:终止命令
  • adb shell
  • ps 查看进程的,查看某一进程adb shell ps <packageName>而非adb shell ps | grep <packageName>
  • top 查看占用率 top -d 1 | grep com.baidu.BaiduMap(以百度地图为例,每一秒打印一次资源利用情况)
  • adb shell dumpsys <service name>打印当前系统信息。
  • adb shell dumpsys 输出信息的开始部分就是所有运行的service;
  • adb shell service list 同样查看所以运行的service
    查询到运行的system service后,就可以在dumpsys后面加上service的名字,查看指定的service信息。
    adb shell dumpsys activity
    adb shell dumpsys cpuinfo
    adb shell dumpsys battery
    adb shell dumpsys window(最后部分可以看到分辨率的信息)
    有些service能够接收额外的参数,我们可以使用-h查看帮助信息。
    adb shell dumpsys package -h
    adb shell dumpsys activity -h
  • adb shell cat /proc/cpuinfo 查看CPU信息
  • adb shell cat /proc/stat 查看CPU使用率
  • adb shell dumpsys gfxinfo <packageName>查看帧率FPS
  • adb shell pm list package列出所有的包名
  • adb shell dumpsys package:列出所有的安装应用的信息
  • adb shell dumpsys package <packageName>:查看某个包的具体信息

更多内容请参考:ADB Shell Commands

维度分析


CPU占用率

  • CPU使用背景知识

    在开发过程中,我们会遇到手机的CPU使用率而引发的问题,那接下来,我对这方面知识做些整理及归纳:
    CPU利用率是指:CPU执行非系统空闲进程的时间 / CPU总的执行时间。
    Android关于进程使用率的限制:
    前台进程不超过95%,后台进程5%, 但是在系统没有前台进程时,后台进程可以超过5%
    (关于什么是前/后台进程,我就不啰嗦了)

  • 可能引发的问题

    整体性能降低
    界面卡顿
    响应慢,容易引起ANR

  • 数据获取

  • 通过ADT中的DDMS来查看

    System Information(CPU Load & Memory usage & Frame Render Time)
  • adb shell $ dumpsys cpuinfo|grep <package>
  • 扩展阅读

  • top命令如下
    130|shell@hnSCL-Q:/ $ top -h
    Usage: top [ -m max_procs ] [ -n iterations ] [ -d delay ] [ -s sort_column ] [
    -t ] [ -h ]
      -m num  Maximum number of processes to display.// 最多显示多少个进程
      -n num  Updates to show before exiting.// 刷新次数 
      -d num  Seconds to wait between updates.// 刷新间隔时间(默认5秒) 
      -s col  Column to sort by (cpu,vss,rss,thr).// 按哪列排序 
      -t      Show threads instead of processes. // 显示线程信息而不是进程 
      -h      Display this help screen.// 显示帮助文档
  • 查看前5个进程CPU情况
shell@hnSCL-Q:/ $ top -m 5 -s cpu

User 1%, System 2%, IOW 0%, IRQ 0%
User 9 + Nice 1 + Sys 18 + Idle 595 + IOW 0 + IRQ 0 + SIRQ 0 = 623

  PID PR CPU% S  #THR     VSS     RSS PCY UID      Name
11003  0   2% R     1   2788K   1228K unk shell    top
  409  0   0% S     8   9864K   1376K  fg root     /system/bin/jankservice
  872  0   0% S   172 1885420K 199516K unk system   system_server
  207  2   0% S     1      0K      0K  fg root     ksoftirqd/2
 2904  0   0% S     6   5708K    276K unk shell    /sbin/adbd

注:PCY显示是前台进程(fg)还是后台进程(bg)的

日志说明(来自:Android应用性能测试

User 35%, System 13%, IOW 0%, IRQ 0% // CPU占用率 
User 109 + Nice 0 + Sys 40 + Idle 156 + IOW 0 + IRQ 0 + SIRQ 1 = 306 // CPU使用情况 

PID CPU% S #THR VSS RSS PCY UID Name // 进程属性 
xx  xx% x   xx  xx  xx  xx  xx   xx 

CPU占用率: 
User    用户进程 
System  系统进程 
IOW IO等待时间 
IRQ 硬中断时间 

CPU使用情况(指一个最小时间片内所占时间,单位jiffies。或者指所占进程数): 
User    处于用户态的运行时间,不包含优先值为负进程 
Nice    优先值为负的进程所占用的CPU时间 
Sys 处于核心态的运行时间 
Idle    除IO等待时间以外的其它等待时间 
IOW IO等待时间 
IRQ 硬中断时间 
SIRQ    软中断时间 

进程属性: 
PID 进程在系统中的ID 
CPU%    当前瞬时所以使用CPU占用率 
S   进程的状态,其中S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值是负数。 
#THR    程序当前所用的线程数 
VSS Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) 
RSS Resident Set Size 实际使用物理内存(包含共享库占用的内存) 
PCY OOXX,不知道什么东东 
UID 运行当前进程的用户id 
Name    程序名称android.process.media 

// ps:内存占用大小有如下规律:VSS >= RSS >= PSS >= USS 
// PSS  Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存) 
// USS  Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
  • 查看CPU信息adb shell cat /proc/cpuinfo
  • 查看CPU总使用率 :在/proc/stat 下有详细的CPU使用情况,可以使用命令adb shell cat /proc/stat
    shell@hnSCL-Q:/ $ cat proc/stat
    cpu  17082742 1298092 14400241 128401698 1072426 4115 524675 0 0 0

    CPU后面的几位数字分别是

    user 从系统启动开始累计到当前时刻,处于用户态的运行时间,不包含 nice值为负进程。
    nice 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间
    system 从系统启动开始累计到当前时刻,处于核心态的运行时间
    idle 从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间
    iowait 从系统启动开始累计到当前时刻,IO等待时间
    irq 从系统启动开始累计到当前时刻,硬中断时间
    softirq 从系统启动开始累计到当前时刻,软中断时间

所以totalCpuTime这个7个属性的和.
CPU总数用率的算法是:100*((totalCpuTimeS-totalCpuTimeF) -(idelS-idelF))/ (totalCpuTimeS-totalCpuTimeF)

详细内容参考:Android获取cpu使用率,剩余内存和硬盘容量

  • 查看当前进程的CPU使用率:/proc/pid/stat下则是该pid的CPU使用情况。先找到自己的进程pid,然后再查看(例如查看com.le.bbs)
    shell@hnSCL-Q:/ $ ps com.le.bbs
    USER     PID   PPID  VSIZE  RSS     WCHAN    PC        NAME
    u0_a170   961   374   1603376 96532 ffffffff 00000000 S com.le.bbs
    shell@hnSCL-Q:/ $ cat /proc/961/stat
    961 (com.le.bbs) S 374 374 0 0 -1 4194624 83875 0 1 0 2785 532 0 0 20 0 28 0 698
    196143 1641857024 24133 4294967295 1 1 0 0 0 0 4612 0 38136 4294967295 0 0 17 1
    0 0 0 0 0 0 0 0 0 0 0 0 0
    shell@hnSCL-Q:/ $

    其中2785 532 0 0 四个数字分别是
    utime 该任务在用户运行状态的时间
    stime 该任务在核心运行的时间
    cutime 所有已死线程在用户状态运行状态的时间
    cstime 所有已死线程在核心的运行时间

所以processCpuTime为这个四个属性的和。当前进行所占CPU的算法是:100*(processCpuTimeS-processCpuTimeF)/(totalCpuTimeS-totalCpuTimeF)

详细内容参考:Android获取cpu使用率,剩余内存和硬盘容量

内存使用

  • 内存限制

    adb shell进入手机,这此参数被纪录在/system/build.prop中,如果想直接查看可以使用adb shell getprop
    dalvik.vm.heapgrowthlimit 单个应用程序最大内存限制,超过这个值会产生OOM
    dalvik.vm.heapstartsize 应用启动后分配的初始内存
    dalvik.vm.heapsize 单个java虚拟机最大的内存限制,超过这个值会产生OOM

    以下是在dos中,在linux中可以直接adb shell getprop|grep

    C:\Users\bugeikan :-)>adb shell
    shell@hnSCL-Q:/ $ cat /system/build.prop|grep heapgrowthlimit
    dalvik.vm.heapgrowthlimit=192m
    shell@hnSCL-Q:/ $ cat /system/build.prop|grep heapstartsize
    dalvik.vm.heapstartsize=14m
    shell@hnSCL-Q:/ $ cat /system/build.prop|grep heapsize
    dalvik.vm.heapsize=512m
    shell@hnSCL-Q:/ $
  • 数据获取

  • 查看内存使用情况
shell@hnSCL-Q:/ $ dumpsys meminfo com.le.bbs
Applications Memory Usage (kB):
Uptime: 1135059305 Realtime: 7004018879

** MEMINFO in pid 3336 [com.le.bbs] **
                   Pss  Private  Private  Swapped     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap     4169     4136        0        0     5748     5427      320
  Dalvik Heap    23306    23028        0        0    34152    29625     4527
 Dalvik Other      578      576        0        0
        Stack      220      220        0        0
      Gfx dev     9600     9600        0        0
    Other dev        5        0        4        0
     .so mmap      728      188      144        0
    .apk mmap      260        0       44        0
    .ttf mmap      252        0      128        0
    .dex mmap     6444        0     6320        0
    .oat mmap     1459        0      368        0
    .art mmap     1210      948        0        0
   Other mmap       24        4        8        0
      Unknown      129      128        0        0
        TOTAL    48384    38828     7016        0    39900    35052     4847

 Objects
               Views:      133         ViewRootImpl:        1
         AppContexts:        3           Activities:        1
              Assets:        3        AssetManagers:        3
       Local Binders:        7        Proxy Binders:       12
       Parcel memory:        3         Parcel count:       12
    Death Recipients:        0      OpenSSL Sockets:        0

 SQL
         MEMORY_USED:        0
  PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0

shell@hnSCL-Q:/ $
  • 查看内存使用率状况adb shell top
User 1%, System 3%, IOW 0%, IRQ 0%

User 8 + Nice 0 + Sys 21 + Idle 599 + IOW 0 + IRQ 0 + SIRQ 0 = 628

  PID PR CPU% S  #THR     VSS     RSS PCY UID      Name

27047  0   2% R     1   2800K   1240K unk shell    top

  409  0   0% S     8   9864K   1376K  fg root     /system/bin/jankservice

 9883  2   0% S     6   5812K    424K unk shell    /sbin/adbd

13754  3   0% S    39 1659460K  59560K  bg u0_a68   com.tencent.mobileqq:MSF

注:PCY显示是前台进程(fg)还是后台进程(bg)的

  • 内存耗用:VSS/RSS/PSS/USS 的介绍

    VSS – Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
    RSS – Resident Set Size 实际使用物理内存(包含共享库占用的内存)
    PSS – Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
    USS – Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

响应时间/加载速度

响应时间主要分为3类

  1. 首次启动 –应用首次启动所花费的时间
  2. 非首次启动 –应用非首次启动所花费的时间
  3. 应用界面切换
  • 数据来源

  • adb shell am start -W <pkg/activity> 可参考Android_adb shell am/pm使用

    启动某个Activity时间
  • 通过使用android提供的DisplayManager来获取所有Activity的启动时间。
C:\Users\yuhushuan>adb logcat > d:\test\logcat.log
^C
C:\Users\yuhushuan>d:
D:\>cd test
D:\test>find "Displayed" logcat.log > all.log //通过“Displayed”过滤

显示性能

  • 性能指标

指标名称 指标意义 基础数据来源 采集方式 用途
FPS 系统合成帧率 SurfaceFlinger adb shell 监控
Aggregate frame stats 应用跳帧次数、幅度 FrameInfo adb shell 监控/上报
Jankiness count (估算)应用跳帧次数 FrameInfo(128帧) adb shell 定位
Max accumulated frames (估算)应用跳帧幅度 FrameInfo(128帧) adb shell 定位
SM 应用绘制轮询频率 Choreographer 多种方式 监控
Skipped frames 应用跳帧次数、幅度 Choreographer 多种 监控/定位/上报
  • 数据来源

    命令行获取
    adb shell dumpsys gfxinfo <PackageName>主要数据来源:gfxinfo(Profile data in ms)

    adb shell dumpsys SurfaceFlinger --latency <pkg/activity> Android性能测试之fps获取

    数据展示
  • 数据参考

    1. FrameInfo 相关指标无法直接进行缺陷定位,但 FrameInfo 当中包含了大量详尽的绘制基础数据,对于缺陷定位也有较大帮助;
    2. 关于缺陷定位过程中连续掉帧阈值的选取,可参考维基百科中提到几个重要的帧率数值:
      • 12 fps:由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约10-12帧的时候,就会认为是连贯的
      • 24 fps:有声电影的拍摄及播放帧率均为每秒24帧,对一般人而言已算可接受
      • 30 fps:早期的高动态电子游戏,帧率少于每秒30帧的话就会显得不连贯,这是因为没有动态模糊使流畅度降低
      • 60 fps:在实际体验中,60帧相对于30帧有着更好的体验

      以上各数据分别对应: 0 帧、1帧、2.5帧、5~6帧。(这就是为啥选择3/6的原因)

  • 相关阅读

    Fps Versus Frame Time
    量化和优化用户与 Android 设备之间的交互
    测试显示性能-基于Android M 开发者预览版

电量、流量

  • 流量测试,同样需要引入几个名词
    中等负荷:应用正常操作
    高负荷:应用极限操作流量测试包括以下测试项:
    a、应用首次启动流量提示
    b、应用后台连续运行2小时的流量值
    c、应用高负荷运行的流量峰值
    d、应用中等负荷运行时的流量均值
  • 数据来源

  • 通过PID
D:\test>adb shell ps com.le.bbs
USER     PID   PPID  VSIZE  RSS     WCHAN    PC        NAME
u0_a170   29273 374   1679132 173580 ffffffff 00000000 S com.le.bbs

D:\test>adb shell cat /proc/29273/net/dev > net.txt

D:\test>

以下为net.txt内容:

Inter-|   Receive                                                |  Transmit

 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed

r_rmnet_data7:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

r_rmnet_data4:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

 wlan0: 36971167   30768    0    5    0     0          0         0  1731561   20200    0    0    0     0       0          0

r_rmnet_data1:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

  p2p0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

  sit0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

dummy0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet0: 157348588  265691    0    0    0     0          0         0 41547752  311861    0    0    0     0       0          0

r_rmnet_data6:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

r_rmnet_data3:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

    lo: 279636994  548567    0    0    0     0          0         0 279636994  548567    0    0    0     0       0          0

rmnet_data7:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet_data6:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet_data5:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet_data4:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet_data3:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet_data2:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet_data1:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

rmnet_data0: 152976131  263638    0    0    0     0          0         0 41547752  311861    0    0    0     0       0          0

r_rmnet_data8:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

r_rmnet_data0:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

r_rmnet_data5:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

r_rmnet_data2:       0       0    0    0    0     0          0         0        0       0    0    0    0     0       0          0

这边的wlan0代表wifi 上传下载量标识! 上传下载量单位是字节可以/1024换算成KB
这里可以看到下载的字节数 、数据包 和 发送的字节数 、数据包

小技巧:wlan0这些值如何初始化0 很简单 你打开手机飞行模式再关掉就清0了

  • 通过UID
    adb shell cat /proc/<pid>/status 测试pid=29273,查看结果uid=10170
    adb shell cat /proc/net/xt_qtaguid/stats > allnet.txt
    find "10170" allnet.txt
D:\test>adb shell cat /proc/net/xt_qtaguid/stats > allnet.txt

D:\test>find "10170" allnet.txt

---------- ALLNET.TXT
idx iface acct_tag_hex uid_tag_int cnt_set rx_bytes rx_packets tx_bytes tx_packets rx_tcp_bytes rx_tcp_packets rx_udp_bytes rx_udp_packets rx_other_bytes rx_other_packets tx_tcp_bytes tx_tcp_packets tx_udp_bytes tx_udp_packets tx_other_bytes tx_other_packets

56 wlan0 0x0 10170 0 10729 55 6033 100 10729 55 0 0 0 0 6033 100 0 0 0 0
57 wlan0 0x0 10170 1 3245372 3410 254265 3412 3245372 3410 0 0 0 0 254265 3412 0
 0 0 0
154 rmnet_data0 0x0 10170 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
155 rmnet_data0 0x0 10170 1 2261 4 482 5 2261 4 0 0 0 0 482 5 0 0 0 0

其中第6和8列为 rx_bytes(接收数据)和tx_bytes(传输数据)包含tcp,udp等所有网络流量传输的统计。一个uid可能对应多个 进程,所以这有两行流量是累加的就求和就行。

Crash

  • 数据获取

    将crash log上报给服务器,不再赘述
    第三方工具:友盟统计、百度统计

ANR

第三方测试工具


  • GT
  • 安测试
  • 腾讯APT

adb top 监控测试apk的cpu资源的取巧写法

示例:

top -m 1 -s cpu -d 2 | grep hudongba
3291 u0_a75 10 -10 19% S 57 1147068K 142920K fg com.jootun.hudongba
3291 u0_a75 10 -10 0% S 56 1160464K 149992K fg com.jootun.hudongba
3291 u0_a75 10 -10 0% S 56 1160584K 150008K fg com.jootun.hudongba
3291 u0_a75 10 -10 31% S 56 1156100K 144092K fg com.jootun.hudongba
3291 u0_a75 10 -10 0% S 56 1159572K 148568K fg com.jootun.hudongba
3291 u0_a75 10 -10 28% S 56 1160572K 152036K fg com.jootun.hudongba
3291 u0_a75 10 -10 0% S 56 1160376K 154824K fg com.jootun.hudongba
3291 u0_a75 10 -10 0% S 56 1160496K 149908K fg com.jootun.hudongba
3291 u0_a75 10 -10 14% S 58 1162868K 151312K fg com.jootun.hudongba
3291 u0_a75 10 -10 19% S 58 1164976K 153944K fg com.jootun.hudongba
3291 u0_a75 10 -10 7% S 58 1165376K 154756K fg com.jootun.hudongba

效果:

每2秒刷新一次安卓机上的项目进程的cpu占有率(只显示最高的那个)

如果cpu占有率最高是目标进程时,就会刷新出来(只显示最多的那个进程,可能是别的,就不会刷新出新行)

解释:

top 是查看资源的命令

-m 1 最多显示1个进程

-s cpu 按cpu占有率排序

-d 2 每2秒刷新一次

grep hudongba 管道过滤出hudongba关键词的信息

 

内存:

另外内存限制的机型参数可以查看,

/system/build.prop
dalvik.vm.heapgrowthlimit 单个应用程序最大内存限制,超过这个值会产生OOM
dalvik.vm.heapstartsize 应用启动后分配的初始内存
dalvik.vm.heapsize 单个java虚拟机最大的内存限制,超过这个值会产生OOM

supersu被收购一年后,它终于开始变了

最后的那一刻,它还是很极客的,高傲自我,

 super su之前很干净

今天看到更新提示,点开后看到它正开始了新生命,迎来新东家对它的期望,一个中国公司会做什么…别人花钱收购的,想怎么做都是无可厚非
新版supersu的各种权限要求

关于Windows文本的回车换行和Unix的换行说法

关于“回车”(carriage return)和“换行”(line feed)这两个概念的来历和区别。

在计算机还没有出现之前,有一种叫做电传打字机(Teletype Model 33)的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新 的字符传过来,那么这个字符将丢失。

于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”,告诉打字机把打印头定位在左边界;另一个叫做“换行”,告诉打字机把纸向下移一行。这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。

后来,计算机发明了,这两个概念也就被般到了计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。

Unix系统里,每行结尾只有“<换行>”,即“\n”;Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;Mac系统里,每行结尾是“<回车>”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。

 

转自链接 http://www.cnblogs.com/zmlctt/p/3710302.html

一次传参太多的思考让我瞬间明白了啥是面向对象开发

早上看到一堆文件的版本号写错了,手动一个一个改很笨,于是搜了下,用python写了个方法,处理指定目录下的所有文件名的某个关键词改为另个关键词……

在设计方法的形参时,发现要传路径,旧词,新词,这个方法循环处理时,还要多传一个文件名,脑中闪过和开发一起商量bug时,他们好像传了一个对象进去……

又想起昨天查js时,打avalon的model各种属性和对象方法……

瞬间明白了啥叫面向对象开发,我之前虽然封装了各种方法和类,然而我在调用和返回时,一直在用变量传递,以前我以为写了类就叫面向对象……然而这是不完整的,因为我一直在操作变量,而不是对象,我的所有业务基于字符串,数组,字典,很少有对象。

只有一些专类才用对象操作,最明显的是time和log两个自己开发对象,有各种属性,各种方法。

有了这个想法了,我准备调整自己设计的自动化框架,改造为真正的对象操作。

三大文本处理工具grep、sed及awk的简单介绍

grep、sed和awk都是文本处理工具,虽然都是文本处理工具单却都有各自的优缺点,一种文本处理命令是不能被另一个完全替换的,否则也不会出现三个文本处理命令了。只不过,相比较而言,sed和awk功能更强大而已,且已独立成一种语言来介绍。

grep:文本过滤器,如果仅仅是过滤文本,可使用grep,其效率要比其他的高很多;

sed:Stream EDitor,流编辑器,默认只处理模式空间,不处理原数据,如果你处理的数据是针对行进行处理的,可以使用sed;
awk:报告生成器,格式化以后显示。如果对处理的数据需要生成报告之类的信息,或者你处理的数据是按列进行处理的,最好使用awk。
grep

grep是一个最初用于Unix操作系统的命令行工具。在给出文件列表或标准输入后,grep会对匹配一个或多个正则表达式的文本进行搜索,并只输出匹配(或者不匹配)的行或文本。Unix的grep家族包括grep、egrep和fgrep。

egrep和fgrep的命令只跟grep有很小不同。egrep是grep的扩展,支持更多的re元字符,fgrep就是fixed grep或fast grep,它们把所有的字母都看作单词,也就是说,正则表达式中的元字符表示回其自身的字面意义,不再特殊。linux使用GNU版本的grep。它功能更强,可以通过-E、-F命令行选项来使用egrep和fgrep的功能。
grep的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到屏幕,不影响原文件内容。

grep可用于shell脚本,因为grep通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回0,如果搜索不成功,则返回1,如果搜索的文件不存在,则返回2。我们利用这些返回值就可进行一些自动化的文本处理工作。

grep:根据模式搜索文本,并将符合模式的文本行显示出来。
Pattern:文本字符和正则表达式的元字符组合而成匹配条件

使用格式:
grep [options] PATTERN [FILE…]
-i:忽略大小写
–color:匹配到字符用其他颜色显示出来,默认是红色
-v:显示没有被模式匹配到的行
-o:只显示被模式匹配到的字符串,不显示行
-E:使用扩展正则表达式
-A n:表示显示该行及其后n行
-B n:表示显示该行及其前n行
-C n:表示显示该行及其前后各n行
正则表达式:REGular EXPression,REGEXP
元字符:
.:匹配任意单个字符
[]:匹配指定范围内的任意单个字符
[^]:匹配指定范围外的任意单个字符
字符集和:[:digit:],[:lower:],[:upper:],[:punct:],[:space:],[:alpha:],[:alnum:]
对应上边:数字  ,小写字母,大写字母,标点符号,空白字符,所有字母,所有数字和字母

匹配次数(贪婪模式,即尽可能长的匹配):
*:匹配其前面的字符任意次
如:编辑文件abc,输入这些字符:a,b,ab,aab,acb,adb,amnb,使用grep匹配a*b,命令及显示效果如下所示:

从上图可以看到,虽然像acb、adb之类的字符串也可以显示出来,但从显示的颜色可以看到,匹配的仅仅是字符b,虽然整个字符串中也有字符a,但是此时a并没有与b一起紧挨着出现,所以仅匹配到字符b;而像ab、aab等字符串,全部显示,此时a紧挨着b出现,符合条件,所以可以显示。

.*:任意长度的任意字符

    如:匹配.b和.*b,看二者有什么区别,命令和显示效果如下:

\?:匹配其前面的字符1次或0次

    如:继续上边的例子,现在匹配a\?b,显示效果如下所示:

\{m,n\}:匹配其前面的字符至少m次,至多n次

\{1,\}:至少一次
\{0,3\}:最多三次

位置锚定:
^:锚定行首,此字符后面的任意内容必须出现在行首
$:锚定行尾,此字符前面的任意内容必须出现在行尾
^$:空白行
\<或\b:锚定词首,其后面的任意字符必须作为单词首部出现
\>或\b:锚定词尾,其后边的任意字符必须作为单词尾部出现

如:\<root\>:在整个文件中,把root作为整个单词出现,词首词尾都不行,如:mroot、rooter均将不匹配
举几个例子来说明下上边的位置锚定。

分组:

\(\)
如: \(ab\)*:ab整体作为匹配字符,且匹配任意次
\(ab\)\{1,\}:ab整体作为匹配字符,且匹配至少一次

          \(ab\):ab整体作为匹配字符
后向引用
\1:匹配第一个左括号以及与之对应的右括号所包括的所有内容
\2:匹配第二个左括号以及与之对应的右括号所包括的所有内容
\3:匹配第三个左括号以及与之对应的右括号所包括的所有内容
……
如:
新建文件test1,添加内容如下:
He love his lover.
She like her liker.
He love his liker.
She like her lover.
He like her.
She love he.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vim test1     #编辑该文件添加如下内容:
He love his lover.
She like her liker.
He love his liker.
She like her lover.
He like her.
She love he.
[root@www ~]# grep '\(l..e\).*\1' test1    #匹配前边第一个左括号的以及与之对应的右括号的内容
He love his lover.
She like her liker.
[root@www ~]# grep 'l..e' test1          #匹配指定的字符,中间可为任意两个字符
He love his lover.
She like her liker.
He love his liker.
She like her lover.
He like her.
She love he.

grep练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1、显示/proc/meminfo文件中以不区分大小的s开头的行;
     grep -i '^s' /proc/meminfo    或者
     grep '^[sS]' /proc/meminfo   #[]表示匹配指定范围内的单个字符,因此也可实现不区分大小写
2、显示/etc/passwd中以nologin结尾的行;
     grep 'nologin$' /etc/passwd
     扩展一:取出默认shell为/sbin/nologin的用户列表
           grep '/sbin/nologin' /etc/passwd | cut -d: -f1    或者
           grep '/sbin/nologin' /etc/passwd | awk -F: '{print $1}'     或者直接使用awk
           awk -F: '$7 ~ /nologin/{print $1}' /etc/passwd
     扩展二:取出默认shell为bash,且其用户ID号最小的用户的用户名
           grep 'bash$' /etc/passwd | sort -n -t: -k3 | head -1 | cut -d: -f1   或者
           awk -F: '$7 ~ /bash/{print $3,$1}' /etc/passwd | sort -n | head -1 | awk '{print $2}'
3、显示/etc/inittab中以#开头,且后面跟一个或多个空白字符,而后又跟了任意非空白字符的行;
      grep '^#[[:space:]]\{1,\}[^[:space:]]' /etc/inittab
4、显示/etc/inittab中包含了:一个数字:(即两个冒号中间一个数字)的行;
      grep ':[0-9]:' /etc/inittab
5、显示/boot/grub/grub.conf文件中以一个或多个空白字符开头的行;
      grep '^[[:space:]]\{1,\}' /boot/grub/grub.conf
6、显示/etc/inittab文件中以一个数字开头并以一个与开头数字相同的数字结尾的行;
      grep '\(^[0-9]\).*\1$' /etc/inittab     #在RHEL5.8以前的版本中可查看到效果
7、找出某文件中的,1位数,或2位数;
      grep '\<[[:digit:]][[:digit:]]\?\>' /etc/inittab  或者
      grep '\<[0-9]\{1,2\}\>' /etc/inittab
8、查找当前系统上名字为student(必须出现在行首)的用户的帐号的相关信息, 文件为/etc/passwd
      grep '^student:' /etc/passwd
      扩展:若存在该用户,找出该用户的ID号:
            grep '^student:' /etc/passwd | cut -d: -f3  或者# id -u student

思考题:用多种方法找出本地的IP地址,这里给出三种方法,如果你还有其他方法可以一起分享下:

1
2
3
ifconfig eth0|grep -oE '([0-9]{1,3}\.?){4}'|head -n 1
ifconfig eth0|awk -F: '/inet addr/{split($2,a," ");print a[1];exit}'     #这里使用了awk的内置函数,如果不懂可在看完awk的介绍后再来做此题
ifconfig |grep "inet addr"|grep -v "127.0.0.1" |awk -F: '{print $2}' |awk '{print $1}'

sed
sed(意为流编辑器,源自英语“stream editor”的缩写)是Unix常见的命令行程序。sed 用来把文档或字符串里面的文字经过一系列编辑命令转换为另一种格式输出。sed 通常用来匹配一个或多个正则表达式的文本进行处理。sed是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

使用格式:
sed ‘AddressCommand’ file …
-n:静默模式,不再显示模式空间中的内容
-i:直接修改原文件
-e SCRIPT -e SCRIPT:添加脚本到将要执行的命令中,可以同时执行多个脚本
-f /PATH/TO/SED_SCORIPT:添加脚本文件中的内容到将要执行的命令中
#sed -f /path/to/script file
-r:表示使用扩展正则表达式Address:
1、StartLine,EndLine:开始行,结束行
如:1,100:表示从第1行到第100行
$;最后一行
2、/RegExp/:扩展正则表达式
如:/^root/
3、/pattern1/,/pattern2/:表示第一次被pattern1匹配到的行开始,至第一次被pattern2匹配到的行结束,这中间的所有行
4、LIneNumber:指定的行
5、StartLIne,+N:从StartLine开始,向后的N行Command:
d:删除符合条件的行;
p:显示符合条件的行,在不使用-n选项时被匹配到的行会显示两遍,因为sed处理时会把处理的信息输出
a \string:在指定的行后面追加新行,内容为“string”,
显示两行或多行,在每行后加\n进行换行
i \string:在指定的行前面添加新行,内容为string
r file:将指定的文件的内容添加至符合条件的文件中
w file:将地址指定范围内的行另存至指定的文件中
s/pattern/string/修饰符:查找并替换,默认只替换每行中第一次被模式匹配到的字符串
加修饰符
g:全局替换,如:s/pattern/string/g
i:忽略字符大小写,如:s/pattern/string/i
s///,s###,s@@@都可以,当所使用的分割符号与内容中显示的相同时,需使用转义字符转义
\(\),\1,\2:成组匹配,\1表示匹配第一个‘(’,\2表示匹配第二个‘(’
&:引用模式匹配到的整个串

介绍了那么多,现在从我们的系统中复制个文件,作为我们的测试文件,这里,我们复制/etc/inittab文件,并稍加修改,删掉几行,然后作为测试文件使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cp /etc/inittab ./    #复制该文件到当前目录下,这里当前处于root家目录下。
vim inittab     #编辑该文件,修改此文件,内容如下所示,其内容也可参看下边的图片
# inittab is only used by upstart for the default runlevel.
#
          #此处是一空行,没有任何字符,就光空行添加代码时会被删掉,所以这里加上备注,说明是一空交行,下面相同
# Individual runlevels are started by /etc/init/rc.conf
#
           #此处是一空行,没有任何字符。该文件内容也看参见下边的图片
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#
id:3:initdefault:

用复制并修过的inittab文件作为测试文件,举几个例子:

1
[root@www ~]# sed '1,3d' inittab:表示删除文件的第一到第三行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
[root@www ~]# sed '3,$d' inittab   #表示删除模式空间中的第三到最后一行
# inittab is only used by upstart for the default runlevel.
#
[root@www ~]# sed '/run/d' inittab     #表示删除被run匹配到的行
#
#
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#
id:3:initdefault:
[root@www ~]# sed '/^#/d' inittab     #表示删除文件中以#开头的行
id:3:initdefault:
[root@www ~]# sed -n '/^\//p' inittab    #显示以/开头的行,因为没有这样的行,故不显示任何信息
[root@www ~]# sed '/^#/p' inittab        #显示以#开头的行,可以看到被匹配到的行,均显示了两遍,这是因为sed处理时会把处理的信息输出
# inittab is only used by upstart for the default runlevel.
# inittab is only used by upstart for the default runlevel.
#
#
# Individual runlevels are started by /etc/init/rc.conf
# Individual runlevels are started by /etc/init/rc.conf
#
#
# Default runlevel. The runlevels used are:
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   3 - Full multiuser mode
#   4 - unused
#   4 - unused
#   5 - X11
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#   6 - reboot (Do NOT set initdefault to this)
#
#
id:3:initdefault:
[root@www ~]# sed -n '/^#/p' inittab     #显示以#开头的行,使用-n选项表示仅显示匹配到的行
# inittab is only used by upstart for the default runlevel.
#
# Individual runlevels are started by /etc/init/rc.conf
#
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#
为了后边的演示,创建文件test,添加如下内容:
Welcome to my linux!
This is my world.
How are you?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
vim test#编辑该文件添加如下内容:
Welcome to my linux!
This is my world.
How are you?
[root@www ~]# sed '2r ./test' inittab    #表示将test文件中的内容添加到inittab文件中,且从第二行往后开始添加
# inittab is only used by upstart for the default runlevel.
#
Welcome to my linux!              #新添加的三行内容
This is my world.
How are you?
# Individual runlevels are started by /etc/init/rc.conf
#
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#
id:3:initdefault:
[root@www ~]# sed '1,2r ./test' inittab    #表示将test文件中的内容添加到inittab文件中,且分别 添加在第一和第二行后边
# inittab is only used by upstart for the default runlevel.
Welcome to my linux!              #新添加的三行内容
This is my world.
How are you?
#
Welcome to my linux!                    #新添加的三行内容
This is my world.
How are you?
# Individual runlevels are started by /etc/init/rc.conf
#
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
#
id:3:initdefault:
[root@www ~]# sed 's/linux/LINUX/g' test     #查找该文件中的字符串,然后替换为指定的字符串
Welcome to my LINUX!                #可以看到以替换为大写的linux
This is my world.
How are you?
[root@www ~]# sed 's/y.u/&r/g' test    #查找指定的字符串,并将其替换为在其后加上r,分隔符采用/
Welcome to my linux!
This is my world.
How are your?
[root@www ~]# sed 's@y.u@&r@g' test     #意义同上,分隔符采用@
Welcome to my linux!
This is my world.
How are your?
[root@www ~]# sed 's#y.u#&r#g' test      #意义同上,分隔符采用#
Welcome to my linux!
This is my world.
How are your?

sed练习;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1、删除/etc/grub.conf文件中行首的空白符;
      sed-r 's/^[[:space:]]+//'/etc/grub.conf
2、替换/etc/inittab文件中“id:3:initdefault:”一行中的数字为5;
     sed's/\(id:\)[0-9]\(:initdefault:\)/\15\2/g'/etc/inittab
3、删除/etc/inittab文件中的空白行;
     sed'/^$/d'/etc/inittab
4、删除/etc/inittab文件中开头的#号;
     sed's/^#//g'/etc/inittab
5、删除某文件中开头的#号及其后面的空白字符,但要求#号后面必须有空白符;
     sed's/^#[[:space:]]\{1,\}//g'/etc/inittab或者
     sed-r 's/^#[[:space:]]+//g'/etc/inittab
6、删除某文件中以空白字符后面跟#类的行中的开头的空白字符及#
     sed-r 's/^[[:space:]]+#//g'/etc/inittab
7、取出一个文件路径的目录名称;
     echo"/etc/rc.d/abc/edu/"| sed-r 's@^(/.*/)[^/]+/?@\1@g'#因sed支持扩展正则表达式,在扩展正则表达式中,+表示匹配其前面的字符至少1次
8、取出一个文件路径的最后一个文件名;
     echo"/etc/rc.d/abc/edu/"| sed-r 's@^/.*/([^/]+)/?@\1@g'
awk

awk是一种优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一。这种编程及数据操作语言(其名称得自于它的创始人Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母)的最大功能取决于一个人所拥有的知识。AWK提供了极其强大的功能:可以进行正则表达式的匹配,样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。它具备了一个完整的语言所应具有的几乎所有精美特性。实际上AWK的确拥有自己的语言:AWK程序设计语言,三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。最简单地说,AWK是一种用于处理文本的编程语言工具。我们现在使用最多的是gawk,gawk是AWK的GNU版本。

使用格式:
# awk [options] ‘script’ file1, file2, …
# awk [options] ‘PATTERN { action }’ file1, file2, …

awk的输出:print和printf
一、print
print的使用格式:
print item1, item2, …
要点:
1、各项目之间使用逗号隔开,而输出时则以空白字符分隔;
2、输出的item可以为字符串或数值、当前记录的字段(如$1)、变量或awk的表达式;数值会先转换为字符串,而后再输出;
3、print命令后面的item可以省略,此时其功能相当于print $0, 因此,如果想输出空白行,则需要使用print “”;例子:

1
2
awk'BEGIN { print "line one\nline two\nline three" }'
awk-F: '{ print $1, $7 }'/etc/passwd#等价于:awk -v FS=: '{print $1,$7}' /etc/passwd

二、awk变量

2.1 awk内置变量之记录变量:
FS: field separator,字段分隔符,默认是空白字符;
RS: Record separator,记录分隔符,默认是换行符;
OFS: Output Filed Separator,输出字段分隔符
ORS:Output Row Separator,输出行分隔符
一起来看一个示例:

1
2
3
4
5
vim test.txt   #编辑该文件,添加如下两行信息作为示例使用
welcome to redhat linux.
how are you?
[root@www ~]# awk 'BEGIN{OFS="#"} {print $1,$2}' test.txt    #指定输出时的分隔符
[root@www ~]# awk 'BEGIN{OFS="#"} {print $1,"hello",$2}' test.txt   #指定输出时的分隔符,并添加显示的内容
上边两个命令的显示效果如下所示:
1
2
3
4
5
6
7
8
9
[root@www ~]# awk -v FS=:  -v OFS=# '{print $1,$7}' /etc/passwd    #以:为字段分隔符,以#号为输出分隔符,显示该文件的第一及第七字段的值,这里仅贴出部分显示内容
root#/bin/bash
bin#/sbin/nologin
daemon#/sbin/nologin
adm#/sbin/nologin
lp#/sbin/nologin
sync#/bin/sync
shutdown#/sbin/shutdown
halt#/sbin/halt

2.2 awk内置变量之数据变量:
NR: The number of input records,awk命令所处理的记录数;如果有多个文件,这个数目会把处理的多个文件中行统一计数;
NF:Number of Field,当前记录的字段个数,有时可用来表示最后一个字段
FNR: 与NR不同的是,FNR用于记录正处理的行是当前这一文件中被总共处理的行数;
ARGV: 数组,保存命令行本身这个字符串,如awk ‘{print $0}’ a.txt b.txt这个命令中,ARGV[0]保存awk,ARGV[1]保存a.txt;
ARGC: awk命令的参数的个数;
FILENAME: awk命令所处理的文件的名称;
ENVIRON:当前shell环境变量及其值的关联数组;
如:

1
2
awk'BEGIN{print ENVIRON["PATH"]}'
awk'{print $NF}'test.txt

2.3 用户自定义变量
gawk允许用户自定义自己的变量以便在程序代码中使用,变量名命名规则与大多数编程语言相同,只能使用字母、数字和下划线,且不能以数字开头。gawk变量名称区分字符大小写。2.3.1 在脚本中赋值变量
在gawk中给变量赋值使用赋值语句进行,例如:

1
2
[root@www ~]# awk 'BEGIN{var="variable testing";print var}'   #给变量赋值,并输出变量的值,下边是显示效果
variable testing

2.3.2 在命令行中使用赋值变量
gawk命令也可以在“脚本”外为变量赋值,并在脚本中进行引用。例如,上述的例子还可以改写为:

1
2
[root@www ~]# awk -v var="variable testing" 'BEGIN{print var}'     #与上述的例子一样,显示效果如下
variable testing

三、printf
printf命令的使用格式:
printf format, item1, item2, …
要点:
1、其与print命令的最大不同是,printf需要指定格式;
2、format用于指定后面的每个item的输出格式;
3、printf语句不会自动打印换行,需要显式使用\n换行。

format格式的指示符都以%开头,后跟一个字符;如下:
%c: 显示字符的ASCII码;
%d, %i:十进制整数;
%e, %E:科学计数法显示数值;
%f: 显示浮点数;
%g, %G: 以科学计数法的格式或浮点数的格式显示数值;
%s: 显示字符串;
%u: 无符号整数;
%%: 显示%自身;

修饰符:
N: 显示宽度;
-: 左对齐;
+:显示数值符号;
例子:

1
awk-F: '{printf "%-15s%i\n",$1,$3}'/etc/passwd#使用printf显示该文件中的第一列和第三列,要求第一列左对齐且占用15个字符宽度,第二列显示十进制整数,显示效果如下所示:

四、输出重定向

使用格式:
print items > output-file
print items >> output-file
print items | command
特殊文件描述符:
/dev/stdin:标准输入
/dev/sdtout: 标准输出
/dev/stderr: 错误输出
/dev/fd/N: 某特定文件描述符,如/dev/stdin就相当于/dev/fd/0;
例子:

1
awk-F: '{printf "%-15s %i\n",$1,$3 > "/dev/stderr" }'/etc/passwd#显示效果与上述例子一样,只不过这里是重定向到错误输出,然后显示

六、awk的操作符:
6.1 算术操作符:
-x:负值
+x:转换为数值;
x^y:x的y次方
x**y: x的y次方

x*y:乘法
x/y:除法
x+y:加法
x-y:减法
x%y:取余6.2 字符串操作符:
只有一个,而且不用写出来,用于实现字符串连接;

1
2
[root@www ~]# awk 'BEGIN{print "A" "B"}'     #连接A和B两个字符,使其成为一个字符串,显示效果如下所示:
AB

6.3 赋值操作符:
=
+=
-=
*=
/=
%=
^=
**=
++

需要注意的是,如果某模式为=号,此时使用/=/可能会有语法错误,应以/[=]/替代;

6.4 布尔值
awk中,任何非0值或非空字符串都为真,反之就为假;

6.5 比较操作符:
x < y     True if x is less than y.
x <= y     True if x is less than or equal to y.
x > y     True if x is greater than y.
x >= y     True if x is greater than or equal to y.
x == y     True if x is equal to y.
x != y     True if x is not equal to y.
x ~ y     True if the string x matches the regexp denoted by y. 如果字符串x被y表示的正则表达式匹配,则为真;
x !~ y     True if the string x does not match the regexp denoted by y. 如果字符串x不被y表示的正则表达式匹配,则为真;
subscript in array       True if the array array has an element with the subscript subscript.

如:

1
2
3
[root@www ~]# awk -F: '$1 ~ /^root/{print $1,$3,$4,$NF}' /etc/passwd    #显示该文件中以root开头的行的第一列、第三列、第四列和最后一列,显示效果如下所示:
root 0 0 /bin/bash
[root@www ~]# awk -F: '$3>=400{printf "%-15s%-10i%s\n",$1,$3,$NF}' /etc/passwd #显示UID大于400的行的第一列、第三列和最后一列

6.7 表达式间的逻辑关系符:
&&
||

6.8 条件表达式:
selector?if-true-exp:if-false-exp

6.9 函数调用:
function_name (para1,para2)

七 awk的模式:
awk ‘program’ input-file1 input-file2 …
其中的program为:
pattern { action }
pattern { action }

7.1 常见的pattern类型:
1、Regexp: 正则表达式,格式为/regular expression/
如:

1
2
3
4
5
[root@www ~]# awk -F: '/bash/{print $0}' /etc/passwd    #在/etc/passwd中查找有bash的行,并显示
root:x:0:0:root:/root:/bin/bash
nginx:x:496:493::/home/nginx:/bin/bash
mysql:x:495:492::/home/mysql:/bin/bash
wpuser:x:494:491::/home/wpuser:/bin/bash
2、expresssion: 表达式,其值非0或为非空字符时满足条件,如:$1 ~ /foo/ 或 $1 == “lq2419″,用运算符~(匹配)和~!(不匹配)。
如:

1
2
3
awk-F: '$3>=500{printf "%-15s%s\n",$1,$3}'/etc/passwd
awk-F: '$1 ~ /root/{print $1,$3}'/etc/passwd
     等价于:awk-F: '$1=="root"{print $1,$3}'/etc/passwd
3、Ranges: 指定的匹配范围,格式为pat1,pat24、BEGIN/END:特殊模式,仅在awk命令执行前运行一次或结束前运行一次
如:

1
2
3
4
5
6
7
8
9
[root@www ~]# awk -F: 'BEGIN{print "Username       UID"}$3>=400{printf "%-15s%s\n",$1,$3}' /etc/passwd       #上式表示对于该文件,在开始比较前先输出username和UID作为题头,然后,输出UID大于400的行的第一列和第三列
Username       UID
rtkit          499
pulse          498
saslauth       497
nfsnobody      65534
nginx          496
mysql          495
wpuser         494
现在我想输出/etc/passwd文件中的第一列,那么,有多少种方法可以实现呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
有三中方式均可以实现,命令及显示效果如下:
[root@www ~]#  awk -v FS=: '{print $1}' /etc/passwd    #使用-v选项和FS指定分隔符,然后显示
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
uucp
[root@www ~]# awk -F: '{print $1}' /etc/passwd       #直接使用-F选项指定分隔符,然后显示
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
uucp
[root@www ~]# awk 'BEGIN{FS=":"}{print $1}' /etc/passwd    #使用BEGIN,在运行前指定分隔符,然后显示
root
bin
daemon
adm
lp
sync
shutdown
halt
mail
uucp

5、Empty(空模式):匹配任意输入行;

7.2 常见的Action
1、Expressions:
2、Control statements
3、Compound statements
4、Input statements
5、Output statements

/正则表达式/:使用通配符的扩展集。
关系表达式:可以用下面运算符表中的关系运算符进行操作,可以是字符串或数字的比较,如$2>$1选择第二个字段比第一个字段长的行。

模式匹配表达式:
模式,模式:指定一个行的范围。该语法不能包括BEGIN和END模式。
BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
END:让用户在最后一条输入记录被读取之后发生的动作。

八 控制语句:
8.1 if-else
语法:if (condition) {then-body} else {[ else-body ]}
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@www ~]# awk -F: '{if ($1=="root") print $1, "Admin"; else print $1, "Common User"}' /etc/passwd    #判断如果第一个字段是root,则显示是admin,否则显示是common user,显示结果如下所示:
root Admin
bin Common User
daemon Common User
adm Common User
lp Common User
syncCommon User
shutdownCommon User
halt Common User
[root@www ~]# awk -F: '{if ($1=="root") printf "%-15s%s\n", $1,"Admin"; else printf "%-15s%s\n", $1, "Common User"}' /etc/passwd          #显示效果同上,只不过这里使用printf,显示定义显示的格式
root             Admin
bin              Common User
daemon           Common User
adm              Common User
lp               Common User
syncCommon User
shutdownCommon User
halt             Common User
mail             Common User
uucp             Common User
[root@www ~]# awk -F: -v sum=0 '{if ($3>=400) sum++}{if ($3>=400) printf "%-15s%i\n",$1,$3}END{print "Sum = "sum;}' /etc/passwd                #定义变量sum=0,然后判断如果UID大于400,让sum自加,并且如果UID大于等于400的显示其用户名和UID,结束前输出sum的值
rtkit             499
pulse             498
saslauth          497
nfsnobody         65534
nginx             496
mysql             495
wpuser            494
Sum = 7
8.2 while
语法: while (condition){statement1; statment2; …}

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@www ~]# awk -F: -v i=1 '{while (i<=7) {print $i;i++}}' /etc/passwd    #使用-v选项显示定义变量i=1,当i小于等于7时循环结束,然后,输出第一个字段到第七个字段,当i为7时,正好输出的是第一行的7个字段的值
root
x
0
0
root
/root
/bin/bash
[root@www ~]# awk -F: '{i=1;while (i<=3) {print $i;i++}}' /etc/passwd    #定义变量i,先判断,当i小于等于3时,输出第一到第三个字段的值,所处理的数据时该文件中的每一行数据,而不仅仅是第一行。这里需要注意与上述命令的区别
root
x
0
bin
x
1
daemon
x
2
adm
x
3
lp
x
4

8.3 do-while
语法: do {statement1, statement2, …} while (condition)

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@www ~]# awk -F: '{i=1;do {print $i;i++}while(i<=3)}' /etc/passwd    #意义同上,显示效果虽与上述相同,但与while不同的是,do-while是先执行一趟,然后再判断是否满足条件,也就是说不管条件是否满足,都会先执行一趟;而while中如果条件不满足,则一趟也不会执行
root
x
0
bin
x
1
daemon
x
2
adm
x
3
lp
x
4

8.4 for
语法: for ( variable assignment; condition; iteration process) { statement1, statement2, …}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@www ~]# awk -F: '{for(i=1;i<=3;i++) print $i}' /etc/passwd     #使用for循环,输出各行的前三个字段
root
x
0
bin
x
1
daemon
x
2
adm
x
3
lp
x
4

for循环还可以用来遍历数组元素:
语法: for (i in array) {statement1, statement2, …}

1
2
3
4
5
6
[root@www ~]# awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd      #匹配最后一个字段不空的行,把最后一段当做数组下标,输出各下标的值,及各下标对应的个数,各下标的个数保存在数组中
         /bin/sync:1
         /bin/bash:4
     /sbin/nologin:29
        /sbin/halt:1
    /sbin/shutdown:1

8.5 case
语法:switch (expression) { case VALUE or /REGEXP/: statement1, statement2,… default: statement1, …}

8.6 break 和 continue
常用于循环或case语句中

8.7 next
提前结束对本行文本的处理,并接着处理下一行;例如,下面的命令将显示其ID号为奇数的用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@www ~]# awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd    #UID号对2取余,如果为0,直接处理下一行,否则,输出用户名和UID号,显示效果如下所示:
bin 1
adm 3
sync5
halt 7
operator 11
gopher 13
nobody 99
dbus 81
usbmuxd 113
vcsa 69
rtkit 499
saslauth 497
postfix 89
abrt 173
rpcuser 29
mysql 495

九 awk中使用数组
9.1 数组
array[index-expression]
index-expression可以使用任意字符串;需要注意的是,如果某数据组元素事先不存在,那么在引用其时,awk会自动创建此元素并初始化为空串;因此,要判断某数据组中是否存在某元素,需要使用index in array的方式。

要遍历数组中的每一个元素,需要使用如下的特殊结构:
for (var in array) { statement1, … }
其中,var用于引用数组下标,而不是元素值;
例子:

1
2
netstat-ant | awk'/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
每出现一被/^tcp/模式匹配到的行,数组S[$NF]就加1,NF为当前匹配到的行的最后一个字段,此处用其值做为数组S的元素索引
1
2
3
4
5
6
7
8
9
10
11
[root@www ~]# awk '{IP[$1]++}END{for (A in IP) printf "%-20s%s\n",A,IP[A]}' /var/log/nginx/access.log     #用法与上一个例子相同,用于统计某日志文件中IP地的访问量
172.16.32.30         3
172.16.32.50         31596
172.16.32.52         408
192.168.0.239        1886
172.16.32.0          1010
[root@www ~]# awk 'BEGIN{A["m"]="hello";A["n"]="world";print A["m"],A["n"]}'     #开始前对数组赋值,然后输出数组的值
hello world
[root@www ~]# awk 'BEGIN{A["m"]="hello";A["n"]="world";for (B in A) print A[B]}'      #在开始前对数组赋值,然后使用for循环,把B当做下标,依次输出数组中的值
hello
world

9.2 删除数组变量
从关系数组中删除数组索引需要使用delete命令。使用格式为:
delete  array[index]

十、awk的内置函数
split(string, array [, fieldsep [, seps ] ])
功能:将string表示的字符串以fieldsep为分隔符进行分隔,并将分隔后的结果保存至array为名的数组中;数组下标为从0开始的序列;

1
netstat-ant | awk'/:22\>/{split($5,clients,":");IP[clients[1]]++}END{for(i in IP){print IP[i],i}}'| sort-rn | head-50  #显示效果如下所示:

length([string])
功能:返回string字符串中字符的个数;
substr(string, start [, length])
功能:取string字符串中的子串,从start开始,取length个;start从1开始计数;
system(command)
功能:执行系统command并将结果返回至awk命令
systime()
功能:取系统当前时间
tolower(s)
功能:将s中的所有字母转为小写
toupper(s)
功能:将s中的所有字母转为大写

十一、用户自定义函数
自定义函数使用function关键字。格式如下:
function F_NAME([variable])
{
statements
}

函数还可以使用return语句返回值,格式为“return value”。

在最后,介绍个面试中常被问到的关于awk的一个问题,顺便给出几个好玩的awk命令。有时候面试的时候,会被问到,使用awk编写脚本打印出乘法口诀,牛人当然觉得这小菜一碟,但是,像我们这些菜鸟,可就该抓耳挠腮了,本人经过上网搜索,加上自己尝试,写出了该脚本。先附上脚本及打印效果,以供参考。

1、乘法口诀
1
2
3
4
5
6
7
8
9
10
11
12
vim cfkj.sh   #编辑该文件,输入如下内容:
#!/bin/awk -f
BEGIN{
        for(i=1;i<=9;i++)
        {
        for(m=1;m<=i;m++)
                {
                        printfm"*"i"="m*i"  "
                }
        print
        }
}

2、打印图案

接触过C语言的都知道,我们在刚开始学C语言的时候,有时候为了让你能够学下去,特别是在谭浩强版的C程序设计丛书中,会有很多课后习题,让你用C语言实现打印一些图案等,甚至是一些有意思的像编程实现使各行、各列以及对角线之和都相等之类的图标等等。那如果现在让你用awk来实现,打印一个图案,又该如何实现呢?比方说,使用awk打印如下图案。
         *
***
*****
*******
对于这个我们又该如何实现呢?其实,可使用如下命令实现打印此图案。
1
awk'BEGIN {for(i=1;i<=4;i++) {for(j=1;j<=10-i;j++) {printf " ";}for(j=1;j<=(2*i-1);j++) {printf "*";}printf "\n";}}'

3、使用数字打印中心对称图形
这个就有点难度了。本人也没有想出来,这个是一个朋友给我的,觉得好玩,就拿出来跟大家分享一下。这里直接给出命令及显示效果了,就不吊各位胃口了。

1
2
echo15|awk'{x=8;for(i=1;i<$0;i++){for(j=1;j<=3*($0-i)-(x>0?x:0);j++)printf" ";for(k=i;k>=1;k--)printf"%d ",k;for(l=2;l<=i;l++)printf"%d ",l;printf"\n";x--};\
for(i=1;i<=$0;i++){for(j=1;j<=(i<=$0-10+1?3*(i-1):3*(i-1)+2-(i-$0%10-10*int(($0-10)/10)));j++)printf" ";for(k=$0-i+1;k>=1;k--)printf"%d ",k;for(l=2;l<=$0-i+1;l++)printf"%d ",l;printf"\n"}}'

原作者:呼伦贝尔 http://lq2419.blog.51cto.com/1365130/1238880

python解析txt文件

场景:抓包工具保存的txt文件,解析为dict字典便于后续处理

方法:open,readlines,split

设计:

1.用过open读取txt文件,执行readlines方法获得line内容组成的list

2.对每行line的分割符使用split解析为list,再通过list下标组装成dict

3.遇到特殊行需要根据具体业务进行分支特殊处理

4.txt参数构成不同区块可以建立多层dict以匹配之后的处理

linux shell读取时间变量命名的日志文件

#! /bin/sh

dirMon=`date +%Y%m`
logDay=`date +%Y%m%d`
logPath=/data/testapp/log/order/${dirMon}/order${logDay}
echo $logPath
tail -200f $logPath

执行这个sh文件查特定目录下的特定时间名log文件,

比如这个jlogn.sh,写完后软连接到/usr/bin/jlogn

rm -f /usr/bin/jlogn
ln -s -f /data/testapp/install/jlogn.sh /usr/bin/jlogn

登录服务器后直接打jlogn就可以查看最新日志了

SVN更新失败,路径乱码,以及提示被锁的解决

问题:在于团队文档协作时,出现过异常操作,导致svn无法本地更新。

分析:报错源在于冲突时本地写了一条待处理queue队列和一条lock锁

解决:

1.下载sqlite3随便存个目录,path添加一下

2.项目svn目录的.svn下有个wc.db,执行sqlite3 wc.db,进sql命令行模式

3.删除队列记录:select * from work_queue;就是队列记录,里面就有刚才失败有关的那条,delete from work_queue;就行了(强迫症要精确删的话,加条件)

4.删除锁记录:select * from wc_lock;delete from wc_lock

5.再次更新,一切搞定