博客 / 詳情

返回

⭐《Linux實戰技能100講》個人筆記 - 5. 文本操作篇

[TOC]

正則表達式與文本搜索

元字符

  • . 匹配任意單個字符(單行模式下不匹配換行符)
  • * 匹配前一個字符任意次
  • [] 匹配範圍內任意一個字符
  • ^ 匹配開頭
  • $ 匹配結尾
  • \ 轉義後面的特殊字符

擴展元字符

  • + 先前的項可以匹配一次或多次。
  • ? 先前的項是可選的,最多匹配一次。
  • | 匹配前面或後面的正則表達式, "或"
  • () 分組

重複

一個正則表達式後面可以跟隨多種重複操作符之一。
{n}    先前的項將匹配恰好 n 次。
{n,}   先前的項可以匹配 n 或更多次。
{n,m}  先前的項將匹配至少 n 詞,但是不會超過 m 次


find 命令

遞歸地在目錄中查找文件

find [路徑...] 表達式

表達式
    查找範圍
    -maxdepth <level>    # 目錄遞歸最大層數
    -mindepth <level>    # ?

    按文件名查找
    -name "模式"        # 完整匹配基本的文件名, 使用"通配符"匹配基本的文件名 
    -regex "模式"        # 完整匹配整個路徑(並非單單匹配文件名), 使用"正則"匹配基本的文件名
    -iregex "模式"        # 完整匹配整個路徑(並非單單匹配文件名) , 使用"正則"匹配基本的文件名, 但不區分大小寫
    
    -regextype <reg_type>        # 改變正則表達式語法, 可選: emacs (this is the default),  posix-awk,  posix-basic,  posix-egrep  and  posix-extended
    
    按文件類型查找
    -type 類型        # b 塊設備, c 字符設備, d 目錄, p 命名管道, f 普通文件, l 符號鏈接, s 套接字
    
    按時間查找
    # 數字參數
    # +n        在這之前的
    # -n        在這之後的
    # n            正好處於該時刻
    -daystart        # 從當日0點為基準, 而不是當前時刻, 影響下述幾種時間類型.
 
    -atime <n>        # Access, 最後訪問時間, 單位為天(但實際是以當前時間為基準)
    -ctime <n>        # Change, 文件i節點修改時間, 單位為天
    -mtime <n>        # Modify, 文件內容修改時間, 單位為天
    
    -amin <n>        # 類似 atime, 但單位是分鐘
    -ctim <n>        # 類似 ctime, 但單位是分鐘
    -mmin <n>        # 類似 mtime, 但單位是分鐘
    
    按大小
    -size <n>        # 默認單位是塊(512字節), 支持 k(KB), M(MB), G(GB), 可以用 + - 或無符號, 意義同上面的按時間查找.
    
    按歸屬
    -user <user>    # 按屬主
    -uid <uid>        # 按屬主的id
    
    動作
    -exec 操作 \;        # 執行時無需確認, {} 作為轉義字符會被替換成查找的文件
    -ok 操作 \;        # 類似 -exec, 但是每次操作都會提示確認
    
    運算符(按優先級從高到低排序)
    ()                        # 強制優先
    ! <表達式>                  # 邏輯非, 對<表達式>結果取反, 即不匹配後面條件, 比如 ! -name 表示不匹配指定文件名
    -not <表達式>                  # 邏輯非, 同 ! <表達式>
    <表達式1> <表達式2>        # 邏輯與(默認), 如果前一個<表達式>執行結果為false, 則不會執行後續<表達式>
    <表達式1> -a <表達式2>    # 邏輯與, 同上
    <表達式1> -and <表達式2>    # 邏輯與, 同上
    <表達式1> -o <表達式2>    # 邏輯或
    <表達式1> -or <表達式2>    # 邏輯或
    <表達式1> , <表達式2>        # 前一個表達式的結果不影響後一個表達式的執行
    
cat 僅會修改 access 時間

touch 會同時修改 access, modify, change

chmod 僅會修改 change 時間

注意:

  • 不同參數的前後順序很重要, 比如 -daystart 需要寫在 -atime 等之前, 否則對其不生效.

示例

# 僅刪除昨天的日誌
find /path/to/log -daystart -mtime 1 -exec rm -v {} \;

grep 命令

文本內容過濾(查找)

查找文本中具有關鍵字的一行

説明
    若未提供查找的文件名或是 - 則默認是標準輸入

語法
    grep [選項] 模式 文件...
    grep [選項] (-e 模式 | -f 包含模式的文件) 文件...
    
選項
    模式
    -G, --basic-regexp            # 使用基本正則(默認)
    -E, --extended-regexp        # 使用擴展正則
    -e 模式, --regexp=模式         # 當模式以 - 開頭時, 應使用這種方式
    
    -v, --invert-match            # 反向匹配, 只選擇不匹配的行    
    -i, --ignore-case            # 忽略大小寫    

    -R, -r, --recursive            # 遞歸地讀每一目錄下的所有文件。這樣做和 -d recurse 選項等價。
    
    顯示內容
    -A <n>, --after-context=<n>        # 打印匹配行的後續 n 行
    -B <n>, --before-context=<n>    # 打印匹配行的前面 n 行
    -o, --only-matching                # 只顯示匹配的部分(默認是顯示匹配行的所有內容)
    -n, --line-number                # 同時顯示行號

    修改顯示類型, 不進行通常輸出
    -c                                # 打印匹配到多少行
    -l, --files-with-matches        # 打印匹配的文件名
    -L, --files-without-match        # 打印不匹配的文件名
    
    
        
    

注意:

  • 在輸入選項時儘量使用引號包圍, 避免Shell去解釋, 比如 grep \. 實際上模式是 . 也就是匹配任意字符. 而 grep "\."grep '\.' 才是匹配模式 \.

在基本正則表達式中,元字符 ?, +, {, |, (, 和 ) 喪失了它們的特殊意義;作為替代,使用加反斜槓的 (backslash) 版本 ?, +, {, |, (, 和 ) 。

cut 行切割

在文件的每一行提取片段

cut 選項 [FILE]...

選項
    -d, --delimiter <分隔>        # 以指定分隔符來切割, 分隔符必須是單個字符
    -f, --fields <list>            # 輸出指定位置的字段, 用逗號分隔多個位置, 位置從1開始

uniq 連續重複行處理

刪除排序文件中的"連續"重複行
    默認從標準輸入讀取, 輸出到標準輸出

uniq 選項 [INPUT [OUTPUT]]

選項
    -c, --count        # 在首列顯示重複數量
    -d, --repeated    # 僅顯示重複的行

sort 排序

對文本文件的行排序

sort 選項 [FILE]...

選項
    字段類型
    -n            # 按照數值排序, 默認包含 -b
    -k            # 
    
    -r            # 逆向排序
    
    -b            # 忽略開頭的空格

seq 產生數字序列

產生數字序列

語法
    seq [OPTION]... LAST
    seq [OPTION]... FIRST LAST
    seq [OPTION]... FIRST INCREMENT LAST

tac 倒序顯示

tac [選項] <文件=STDIN>

行編輯器

非交互式, 基於行操作的模式編輯.

sed 行編輯器

sed 是單行文本編輯器, 非交互式.

sed 的模式空間, 其基本工作方式

  1. 將文件以行為單位讀取到內存(稱作模式空間)
  2. 使用sed的每個腳本依次對該行進行操作
  3. 打印模式空間的內容並清空
  4. 讀取下一行, 重複執行上述步驟

sed 的空間示意圖:

image-20200302180855354

  • 模式空間的內容默認會輸出, 並清空
  • 保持空間的默認內容是 \n

Tip

  • 使用 ; 可以替換多個 -e

模式空間 pattern space

s 替換命令
替換

sed [選項] '[<尋址>]s<分隔符><old><分隔符><new><分隔符>[<標誌位>]' [文件...]        # 簡單示例: sed 's/old/new/'

參數
    old        # 支持正則表達式
    分隔符      # 可以採用 / 也可以採用其他來避免與正則匹配內容衝突, 比如 ~ @ 等

尋址(默認是所有行)    
    !            # 對尋址取反, eg. "2,4!d" 表示不刪除2~4行
    <n>            # 只替換第<n>行
    <n1>,<n2>    # 區間: 從<n1>到<n2>這幾行        eg. /12\/Apr\/2020/,/13\/Apr\/2020/      正則同樣可以使用這種區間尋址
    1,<n>        # 替換從開始到第<n>行
    <n>,$        # 替換從第<n>到結束的這些行
    /正則/       # 僅替換符合此正則匹配到的行
                # eg.   sed '/正則/s/old/new/' 
                # eg.  sed '/正則/,$s/old/new/'    (正則可以和行號混用)表示從匹配到的正則那行開始到結束都替換 
                # 尋址可以匹配多條命令, 注意大括號. eg.   /regular/{s/old/new/;s/old/new/}    
                # 比如nginx日誌需要篩選出 14號這天的: sed -n '/14\/Apr\/2020/,/15\/Apr\/2020/ p' xx.log
 
標誌位(默認是隻替換第1個匹配項)
    /g        # 替換所有匹配項(默認只替換第1個匹配項)
    /<n>    # <n>是一個數字, 表示每行只替換第<n>個匹配項
    /p        # 打印模式空間的內容(即匹配的行), 通常會和 -n 一起使用, 如果不和 -n 一起使用, 會導致匹配的行多輸出依次.
            # eg. sed -n 's/old/new/p'                 # 此時僅打印匹配的行
    /w <file>    # 將模式空間的內容(即匹配到的行)寫入到指定文件


選項
    -r, --regexp-extended            # 使用擴展正則表達式, 包括圓括號分組及回調.
    -e script, --expression=script    # 指定多個執行腳本時, 每個腳本前用一個 -e. 可以使用 "分號" 簡寫
                                    # Eg. -e 's/old1/new1/' -e 's/old2/new2/'
    -f script-file, --file=script-file    # 加載腳本文件
    -i[<後綴>], --in-place[=<後綴>]  # 修改原始文件, 當指定後綴時則會生成對應的備份文件. 也可以直接輸出重定向輸出到其他文件
    -n, --quiet, --silent            # 默認不自動打印

示例
    # 使用擴展正則表達式
    sed -r 's/old/new/' [文件]...

    # 執行多個腳本
    sed -e 's/old/new/' -e 's/old/new/' [文件]...
    sed 's/old/new/;s/old/new/'                        # 使用分號隔開不同腳本

    # 將結果寫回文件保存
    sed -i 's/'
    sed -i,.bak 's/'
    
    # 圓括號分組及回調
    echo "a213123t" | sed -r 's/a(\d*)/b\1/g'        # b213123t
d 刪除命令
刪除當前"模式空間的內容", 並放棄後面的命令, 讀取新的內容並重新執行sed
    改變腳本的控制流, 讀取新的輸入行
    (由於模式空間的內容被刪除了, 因此d後面的腳本沒法執行, 會略過)
    (使用 s 替換成空內容, 但本質上這一行內容還在, 依舊會執行後續腳本, 會輸出)

sed '[<尋址>]d' [文件...]

尋址
    同 s 命令

示例
    sed '1d'            # 刪除第一行
    sed '/^\s*#/d'        # 刪除 # 開頭的行
a 追加命令
在匹配行的下一行插入內容

sed '[<尋址>]a <插入內容>'

示例
    sed '1i haha'        # 在原先第1行前面插入 haha
i 插入命令
在匹配行在上一行插入內容

sed '[<尋址>]i <插入內容>'

示例
    sed '2i haha'        # 在原先第二行前面插入 haha
c 改寫命令
將匹配行替換成指定內容
    指定匹配連續幾行時, 只會替換1次    # sed '2,5c <替換內容>'

sed '[<尋址>]c <替換內容>'

示例
    sed '2c hehe'        # 將第2行替換成 "hehe"
r 讀文件並插入 (從文件讀取改寫內容)
在匹配行下面分別插入指定文件中的內容

sed '[<尋址>]r <文件名>'

示例
    sed '$r afile' bfile > cfile        # 將 afile 內容追加到 bfile 結尾併合併成新的文件 cfile
常用於合併多個文件
w 寫文件 ?
?

sed '[<尋址>]w <文件名>'
p 打印

與 替換命令 s 的標誌位 /p 不一樣.

輸出匹配的行(不禁止原始的輸出)

sed [選項] '[<尋址>]p'

示例
    sed -n '<尋址>p'        # 只打印匹配的行
n 提前讀入下一行, 並指向下一行
sed 'n'

示例
    cat <<EOF | sed 'n'
    1
    2
    3
    4
    5
    EOF
    
    # 輸出(由於未作任何操作, 因此原樣輸出)
    1
    2
    3
    4
    5
    
    # ----------------------

    cat <<EOF | sed -n 'n;p'
    1
    2
    3
    4
    5
    EOF
    
    # 輸出
    2
    4

正常模式

img

使用n命令後

img使用n命令後

圖來源: https://blog.51cto.com/studyi...
q 退出命令
遇到匹配項, 在處理完該項後退出

sed '[<尋址>]q'

示例
    sed '2q'        # 在打印完第二行後退出
    sed '/root/q'    # 在匹配到某一行有 root 後退出

打印前N行的一個比較

  • sed 10q filename 讀取前10行就退出
  • sed -n '1,10p' filename 逐行讀入全部, 只顯示1-10行, 更耗時.
= 打印行號
打印出當前行號(單行顯示)
    不影響正常的打印

sed '='

示例
    echo 'a' | sed '='        # 打印結果, 第一行 "1", 第二行 "a"

多行模式空間 pattern space

多行模式空間改變了 sed 的基本流程, 通過使用 N, P, D 讀入並處理多行.

主要應對配置文件是多行的情況, 比如 json 或 xml.

綜合示例

a.txt 內容如下
1
2
3
4
5
6
7
8
9

# ------------------- 示例1 ---------------------#
sed 'N;N;s/\n/\t/g;' a.txt
1    2    3
4    5    6
7    8    9


# ---------------------示例2 打印梯形結構 -------------------#
# 關鍵在於利用 D 改變控制流
sed -n 'P;N;s/\n/\t/;s/^/\n/;D' a.txt
1
1    2
1    2    3
1    2    3    4
1    2    3    4    5
1    2    3    4    5    6
1    2    3    4    5    6    7
1    2    3    4    5    6    7    8
1    2    3    4    5    6    7    8    9
b.txt 內容如下
hell
o bash hel
lo bash


# -------------------- 示例 hello bash 替換成 hello sed --------------#
sed 's/^\s*//;N;s/\n//;s/hello bash/hello sed\n/;P;D;' b.txt
hello sed
hello sed
N 將下一行加入到模式空間
讀取時, 將下一行加入到模式空間
    兩行視為同一行, 但中間有個換行符 \n
    此時多行模式 . 可以匹配到除了末尾的其他換行符

sed 'N'

示例
    sed = <文件> | sed 'N;s/\n/\t/'        # 在每行前面加入行號

使用N命令後

img

圖來源: https://blog.51cto.com/studyi...
D 刪除模式空間中的第一個字符到第一個換行符
注意
    會改變控制流, 不再執行緊隨的後續命令, 會再次回到腳本的初始處(但不清空模式空間)

sed 'D'
P 打印模式空間中的第一個字符到第一個換行符
注意
    僅僅是打印, 並不會刪除打印的部分.

sed 'P'

保持空間 hold space

注意:

  • 保持空間在不存儲東西時, 它裏面的默認內容是 \n

    因此第一次一般是用 h 覆蓋掉保持空間的 \n
  • 保持空間的內容只能臨時存儲和取出, 無法直接修改

綜合示例

# 下述多個都實現了 tac 倒序顯示的效果
# 思路: 每次將本輪正確的結果保存在保持空間
cat -n /etc/passwd | head -n 6 | sed -n '1!G;$!x;$p'
cat -n /etc/passwd | head -n 6 | sed -n '1!G;h;$p'
cat -n /etc/passwd | head -n 6 | sed '1!G;h;$!d'
cat -n /etc/passwd | head -n 6 | sed '1!G;$!x;$!d'
cat -n /etc/passwd | head -n 6 | sed -n '1h;1d;G;h;$p';
cat -n /etc/passwd | head -n 6 | sed -n '1h;1!G;h;$p';

sed '=;6q' /etc/passwd | sed 'N;s/\n/\t/;1!G;h;$!d'

# --------------------- 顯示結果 --------------------#
6    sync:x:5:0:sync:/sbin:/bin/sync
5    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
4    adm:x:3:4:adm:/var/adm:/sbin/nologin
3    daemon:x:2:2:daemon:/sbin:/sbin/nologin
2    bin:x:1:1:bin:/bin:/sbin/nologin
1    root:x:0:0:root:/root:/bin/bash
h 和 H 將模式空間內容存放到保持空間
  • h 是覆蓋
  • H 是追加

注意: 該操作不會清空模式空間(需要清空模式空間可以用 d)

g 和 G 將保持空間內容取出到模式空間
  • g 是覆蓋
  • G 是追加

注意: 該操作不會清空保持空間

x 交換模式空間和保持空間內容

awk 行編輯器

awk 是一種解釋型編碼語言, 常用於處理文本數據.

awk 主要是對 sed 的一個補充, 常用於在sed處理完後對相應結果進行調整並輸出.

awk 版本

  • awk: 初始版本
  • nawk: new awk, 是 awk 的改進增強版
  • gawk: GNU awk, 所有 GNU/Linux 發行版都包含 gawk, 且完全兼容 awk 與 nawk

    實際 centos 用的就是 gawk

參考文檔:

  • The GNU Awk User's Guide

    網上很多教程瞎JB寫, 建議以官方文檔👆 為準

  • 精通awk系列

    非常不錯的系列教程! :+1:

  • https://awk.readthedocs.io/en...

    不完整.

awk 和 sed 的區別

  • awk 更像是腳本語言
  • awk 用於"比較規範"的文本處理, 用於統計數量並調整順序, 輸出指定字段.
  • 使用 sed 將不規範的文本處理為"比較規範"的文本

awk 腳本的流程控制

  1. BEGIN{} 輸入數據前例程, 可選
  2. {} 主輸入循環
  3. END{} 所有文件讀取完成例程

個人理解的執行順序

  1. 執行開始塊(可選) BEGIN{}
  2. 若存在主體塊 {} 或結束塊 END{}`, 則打開文件並讀入數據

    如果文件無法打開會在此時報錯.

    主體塊允許存在多個, 比如根據不同的匹配模式, 寫多個主體塊.

  3. 若存在<尋址>/pattern/, 則會依次匹配, 通過則對該記錄執行 {}
  4. 讀完所有記錄後, 執行結束塊(可選) END{}
特殊情況, 腳本只包含 BEGIN{} 時, 在執行 awk 命令後面傳入參數(非文件名), 此時不會導致報錯, 因為不會執行到步驟2(即嘗試打開文件).

如果腳本只包含 BEGIN{命令} , 好像可以縮寫成 awk '命令' ??

語法


語法
    awk [選項] 'awk腳本內容' [ -- ] [文件...]        # 其中任意一部分都是可選的
    awk [選項] -f <awk腳本文件> [ -- ] [文件...]    # 不直接在命令行中書寫 awk 腳本, 而是從指定文件讀取

選項
    -f <腳本文件>, --file=<腳本文件>     # 從指定腳本文件讀取 awk 腳本, 而不是默認地從第一個命令行參數讀取.
    -F, --field-separator <分隔符>        # 字段分隔符可以使用正則表達式, 是空格 " ", 也可以在awk腳本中用 FS="," 來修改這一行為
    -v <var>="val", --assign <var>="val"  # 直接為awk腳本中的變量賦值, 比如 -v suffix=yjx, 然後代碼中直接就存在 suffix 這個變量了, 且值是 yjx


    --dump-variables[=<file="awkvars.out">]        # 將awk中的所有全局變量及其值導出到指定文件中.
                                      

awk腳本的塊
    開始塊
        BEGIN {}         # 特殊模式: 讀取輸入數據前
    主體塊
        表達式 {}              # 若匹配上才執行後續的 action              
                        # eg. 
                        #        `$1 == "top" {}`
                        #        `NR >= 2 {}`
                        #        'length($0)'
        /正則/ {}           # 正則匹配, 若匹配上才執行後續的 action        
                        # eg. 
                        #        /^menu/    只處理 menu 開頭的記錄 
                        #        /cost/    只處理本行內容中包含 "cost" 的記錄
        !/正則/ {}      # 正則不匹配
        組合模式 {}         # 一個 組合模式 通過與(&&),或(||),非(|),以及括弧來組合多個表達式
        {}                # 每讀取一行數據則執行後續的 action
        模式1,模式2 {}     # 範圍模式(range pattern)匹配從與 模式1 相匹配的行到與 模式2 相匹配的行(包含該行)之間的所有行,對於這些輸入行,執行 語句 。
    結束塊
        END {}            # 特殊模式: 讀取完畢後

    
    
    
主體塊的action
    print    打印(若未配置則默認是 print)
    next    對於本行的處理, 跳過後續步驟表達式
    {...}    執行 {} 中的腳本






示例.
    awk [選項] 'BEGIN{} [<條件>] {} END{}' 文件...    # 其中任意一部分都是可選的
    

                                     # BEGIN{} 輸入數據前例程
                                     # {} 主輸入循環, <尋址> 是應用於 {} 的
                                     # END{} 所有文件讀取完成例程
                                     
    awk -f <腳本.awk>            # 從文件中加載執行的命令, 注意<尋址>要和 { 寫在同一行, 不然好像沒生效?
                             # 示例 xx.awk
                             # BEGIN {
                             #        ...
                             # }
                             # /過濾條件/ {
                             #         ...
                             # }
                             # END {
                             #        ...
                             # }



字段引用
    $0                # 表示記錄整行
    $1 $2 ... $n     # 表示第1~第n個字段
    NF                # 標識 $0 被分割的字段數
    $NF                # 表示最後一個字段, NF 變量表示字段的數量, 比如當前共5個字段, 則 $NF 等價於 $5.

                                    
    
    
簡單示例 
    awk -F ',' '{print $1,$2,$3}' filename     # 逗號分隔, 打印前3個字段
    echo "menuentry 'CentOS Linux (5.5.6) 7 (Core)' --class centos" | awk -F "'" '{print $2}'    # 提取出內核版本

修改字段或NF值的聯動效應

注意以下幾種操作

  • 修改 $0 會根據 FS, 重新劃分字段並自動賦值給 $1, $2, ... , NF.

    $0=$0 也會觸發重新劃分字段的操作.

  • 修改 $1, $2, ... , 會根據 OFS 重新生成 $0, 但不會重新分割.

    即使是 $1=$1 也會觸發上述操作, 當然是用 NF=NF 也是可以的.

    # 利用該特性重新生成去除行首行尾空格, 壓縮中間空格的效果
    echo "   a   b   c  " | awk '{NF=NF; print $0;}'
    
    輸出
    a b c
  • 賦值給不存在的字段, 會新增字段並按需使用空字符串填充中間的字段,並使用 OFS 重新計算$0
  • 增加 NF 值,將使用空字符串新增字段,並使用 OFS 重新計算 $0
  • 減少 NF值,將丟棄一定數量的尾部字段,並使用OFS重新計算$0

正則字面量

這裏有個地方容易被坑到!!!

任何單獨出現的 /pattern/ 都等價於 $0 ~ /pattern/, 這個在將正則表達式賦值給變量時特別容易被坑, 舉例:

  • if(/pattern/) 等價於 if($0 ~ /pattern/)
  • a = /pattern/ 等價於將 $0 ~ /pattern/ 的匹配返回值(0或1)賦值給 a
  • /pattern/ ~ $1 等價於 $0 ~ /pattern/ ~ $1 ,表示用 $1 去匹配0或1
  • /pattern/ 作為參數傳給函數時,傳遞的是 $0~/pattern/ 的結果0或1
匹配成功時返回1, 失敗返回0.

舉例

# 這邊直接用於匹配沒什麼問題
awk 'BEGIN { if ("abc" ~ "^[a-z]+$") { print "match"} }'

#輸出
match



awk 'BEGIN { if ("abc" ~ /^[a-z]+$/) { print "match"} }'

#輸出
match


# 這邊將其賦值給其他變量, 此時其實 regex = $0 ~ /^[a-z]+$/, 也就是值 0
awk 'BEGIN { regex=/^[a-z]+$/; print regex; if ("abc" ~ regex) { print "match"} }'

#輸出
0



awk 'BEGIN { regex="^[a-z]+$"; print regex; if ("abc" ~ regex) { print "match"} }'

#輸出
^[a-z]+$
match

在 awk 中書寫正則可以用 /[0-9]+/ 也可以用

/[0-9]+/
匹配方式:"str" ~ /pattern/或"str" !~ /pattern/
匹配結果返回值為0(匹配失敗)或1(匹配成功)
任何單獨出現的/pattern/都等價於$0 ~ /pattern/
if(/pattern/)等價於if($0 ~ /pattern/)
坑1:a=/pattern/等價於將$0 ~ /pattern/的匹配返回值(0或1)賦值給a
坑2:/pattern/ ~ $1等價於$0 ~ /pattern/ ~ $1,表示用$1去匹配0或1
坑3:/pattern/作為參數傳給函數時,傳遞的是$0~/pat/的結果0或1
坑4.坑5.坑6…

內置變量

awk 中可以看作是在一個獨立的系統空間中, 因此也有其特殊的系統變量.

注意:

  • 字段的引用不能加 $, 這點與Shell不一樣, 不然就變成獲取記錄中某個字段的值了.
控制 AWK 工作的預定義變量
  • FS (field separator)輸入數據的分隔符, 默認值是空格

    awk -F ","
    # 等價於
    awk 'BEGIN {FS=","}'        # 在讀入文件之前設置字段分隔符
  • OFS (output field separator)輸出字段分隔符(默認是空格)

    awk 'BEGIN {OFS=","}'
  • FIELDWIDTHS 以指定寬度切割字段而非按照 FS
  • FPAT 以正則匹配, 將匹配到的結果作為字段, 而非按照 FS 或 FIELDWIDTHS 劃分. 理解為 re_match, 而不是 re_split

    FPAT = "([^,]+)|(\"[^\"]+\")"
    
    # 上述 FPAT 用於分割以逗號分隔的 csv 文件, 若使用雙引號包裹的則視為是一個字段(忽略其中的逗號).
    # 比如對於數據:       abc,"pqr,mno"
    # $1 值為 abc
    # $2 值為 "pqr,mno"
    https://stackoverflow.com/que...
  • RS (record separator)記錄分割符(默認是 \n)

    該變量通常在 BEGIN 塊中修改, 修改該變量可以控制 awk 每次讀取的數據的範圍(默認是讀取一行), 讀取到的記錄是不包含記錄分隔符的.

    • RS 設置為單個字符: 直接使用該字符來分割記錄
    • RS 設置為多個字符: 視為正則(非兼容模式), 使用該正則來分割記錄.
    awk 'BEGIN {RS=":"}'        # 將記錄分割符設置為 :   , 這樣每次遇到 : 時就視為一條記錄分別處理.

特殊讀取需求

    • RS="" : 按段落讀取(這個特性有用)
    • RS="\0" : 一次性讀取所有數據, 但有些特殊文件包含了空字符 \0
    • RS="^$" : 真正的一次性讀取所有數據, 因此 ^$ 匹配的是空文件
    • RS="\n+" : 按行讀取, 但忽略空行
    • ORS (output row separator) 輸出記錄分隔符(默認是 \n)

      awk 'BEGIN {OFS=":"}'
    • CONVFMT 表示數據轉換為字符串的格式, 默認值是 %.6g
    • OFMT 表示數值輸出的格式, 默認值是 %0.6g, 標識有效位(整數部分加小數部分)最多為6.
    • IGNORECASE 控制是否對大小寫敏感, 當該變量設置時, 忽略大小寫. 在分割記錄時也受該變量影響.

      awk 'BEGIN{IGNORECASE=1} /amit/' marks.txt
    攜帶信息的預定義變量
    文件與行號
    • FILENAME 當前被處理的文件名

      BEGIN {} 塊中, 該變量是未定義的.
    • NR (number of rows)記錄的行號

      會一直累加, 就算處理多個文件, 該行號也會一直累加.

    • FNR (file number of rows)記錄的行號(處理不同文件時會重置)

      當處理多個文件時, 切換到不同文件則該值會重置掉.

    • NF (number of fields)字段數量

      最後一個字段內容可以用 $NF 取出
    • ARGIND 用於處理多個文件時, 表示當前正在處理的文件的順序(從 1 開始)

      awk 'ARGIND==1 {if(FNR>3)print FNR,$3 } ARGIND==2 {if(FNR>1)print FNR,$2} ARGIND==3 {if(FNR<3)print FNR,$NF}' s.log t.log s.log
    • RT (Record Termination) 實際記錄分割符

      當 RS 設置為多個字符(正則)時, 在每條記錄被讀取並分割後, RT 變量會被設置為實際用於劃分記錄的字符.

    命令行與環境參數
    • ARGC 命令行位置參數個數("選項"是不包含在內的)
    • ARGV 命令行位置參數數組

      • ARGV[0] 值是命令名本身, 即 awk
      • ARGV[1] 是傳入的第1個參數
      • 範圍: ARGV[0] ~ ARGV[ARGC - 1]
    • ENVIRON 存放系統環境變量的關聯數組

      awk 'BEGIN{print ENVIRON["USER"]}'        # 輸出: shell 變量 "USER"
    • PROCINFO 關聯數組, 保存進程相關的信息

      # 打印 awk 進程的Id
      awk 'BEGIN { print PROCINFO["pid"] }'
    • ERRNO 用於存儲當 getline 重定向失敗或 close 函數調用失敗時的失敗信息.

    表達式

    賦值操作符
    • =

      • = 的左右是可以有空格的.
      • 字符串拼接中的空格會被忽略

        Eg. var = "hello" "world"

        實際上 var 值是 "helloworld", 沒有中間的空格

      • 若是拼接兩個字符串變量, 則使用字符串字面量隔開即可.

        s3=s1""s2

    • ++

      支持前置遞增和後置遞增

    • --

      支持前置遞減增和後置遞減

    • +=
    • -=
    • *=
    • /=
    • %=
    • ^=
    算數操作符
    • +
    • -
    • *
    • /
    • %
    • ^
    位操作
    • AND 按位與操作
    • OR 按位或操作
    • XOR 按位異或操作
    關係操作符
    • <
    • >
    • <=
    • >=
    • ==

      注意, 判斷兩個值是否相等要用 ==, 而不是賦值運算符 =

    • !=
    • ~ 字符匹配正則表達式
    • !~
    布爾操作符
    • &&
    • ||
    • !

      使用 ! 時注意使用括號將相關表達式括起來, 避免寫錯.

    三元運算符

    condition expression ? statement1 : statement2

    匹配運算符

    除了塊的條件匹配外, 還可以用於 if 判斷之類的.

    • ~ 匹配指定正則表達式的, 用於主體塊的條件匹配

      # 僅處理包含 hello 文本的行
      awk '$0 ~ "hello"' xx.txt
      
      
      # 注意這裏正則是用 / / 包圍起來, 而不是雙引號
      awk 'BEGIN { if ("[abc]" ~ /\[.*\]/) { print "match";}}'
      #輸出
      #match
      
      # 注意這裏用雙引號括起來時, 裏面用了雙斜槓來處理正則的 [
      awk 'BEGIN { if ("[abc]" ~ "\\[abc\\]") { print "match";}}'
      #輸出
      #match
      
    • !~ 不匹配指定正則表達式的, 用於主體塊的條件匹配

      # 僅處理不含 hello 文本的行
      awk '$0 !~ "hello"' marks.txt

    條件和循環

    注意:

    • 表達式結果: 0為false, 1為true

      這個與 Shell 是相反的.
    • 影響控制的語句: break, continue

    綜合示例

    cat kpi.txt
    
    user1 70 72 74 76 74 72
    user2 80 82 84 82 80 78
    
    
    #-------------- 計算每行數值的總之和平均值 -----------#
    awk '{total=0; avg=0; for (i=2;i<=NF;i++) {total+=$i;} avg=total/(NF-1); print $1,total,avg;}' kpi.txt
    
    user1 438 73
    user2 486 81
    if 條件語句
    if (表達式) {
    
    } else if (表達式) {
    
    } else {
    
    }
    執行多條語句要用 {}, 只有一條語句時可忽略.
    for 循環
    for (初始值; 循環判斷條件; 累加) {
    }
    while 循環
    while (表達式) {
    
    }
    do 循環
    do {
    
    } while(表達式)

    數組

    普通數組
    • awk 的數組實際上是關聯數組, 可通過下標(數字實際上也是字符串)依次訪問

      比如 arr[1]arr["1"] 實際上是對同一個 key 操作
    • 支持多維數組

      gawk(可以認為是 awk 的加強版, 替代版, 至少 centos 的 awk 實際就是 gawk) 支持真正意義上的多維數組.

      awk 還有一種更"原始"的使用一維數組模擬多維數組的, 但在 gawk 中已經沒必要了.

    # 定義
    ## 下標可以是數字(視為字符串)或字符串
    數組名[下標] = 值
    
    
    # 遍歷
    for (變量 in 數組名) {
        數組名[變量]            # 獲取對應數組值
    }
    
    
    # 刪除數組
    delete 數組
    # 刪除數組元素
    delete 數組[下標]
    
    
    # 判斷數組中是否存在指定"鍵"
    if (key in array)
    # 判斷數組中是否不存在指定"鍵", 注意這裏額外加了一個括號, 不能省略了
    if (!(key in array))
    命令行參數數組
    • ARGC 命令行位置參數個數
    • ARGV 命令行位置參數數組

      • ARGV[0] 值是命令名本身, 即 awk
      • ARGV[1] 是傳入的第1個參數
      • 範圍: ARGV[0] ~ ARGV[ARGC - 1]

    示例

    cat argv.awk
    
    內容
        BEGIN {
            for (i=0; i<ARGC; i++) {
                print ARGV[i];
            }
            print ARGC;
        }
    
    
    # --------------------------------#
    awk -f argv.awk  afile 11 22 33
    
    輸出
        awk        # ARGV[0]
        afile    # ARGV[1]
        11        # ARGV[2]
        22        # ARGV[3]
        33        # ARGV[4]
        5        # ARGC
    此處不會報錯是因為awk腳本中只包含 BEGIN {} 部分, 因此不會將參數視為文件名並嘗試打開.
    數組函數
    • length(數組) 獲取數組長度
    • asort(數組a[, 數組b, ...]) 對數組a的值進行排序,並且會丟掉原先鍵值(重新生成數字遞增的 key 來替代), 並將結果賦予數組 b (若未傳, 則直接修改數組 a).
    • arorti(數組a[, 數組b, ...]) 對數組a的鍵進行排序, 並將結果賦予數組 b (若未傳, 則直接修改數組 a).

    函數

    算術函數
    • sin()
    • cos()
    • atan2(y,x)
    • exp(x) 返回自然數 e 的 x 次方
    • sqrt() 平方根
    • log(x) 計算 x 的自然對數
    • int() 轉換為整數(忽略小數部分)
    • rand() 偽隨機數, 範圍 [0,1), 默認使用 srand(1) 初始化隨機種子.

      若不使用 srand() 會發現每次獲取的所謂隨機數都是一樣的.

      srand(); print rand();

    • srand([seed]) 重置隨機種子, 默認種子採用當前時間的 epoch 值(秒級別)
    位操作函數
    • compl(num) ` 按位求補
    • lshift(num, offset) 左移N位
    • rshift(num, offset) 右移N位
    字符串函數

    awk 中涉及字符索引的函數, 索引位都是從 1 開始.

    注意, 不同 awk 版本, 函數參數個數是有可能不一樣的.

    • sprintf(format, expr1, ...) 返回格式化後的字符串

      示例: a = sprintf("%10s\n", "abc")

    • length(s) 返回字符串/數組的長度
    • strtonum(str) 將字符串轉換為十進制數值

      如果 str 以0開頭,則將其識別為8進制

      如果 str 以0x或0X開頭,則將其識別為16進制

    • tolower(str) 轉換為小寫
    • toupper(str) 轉換為大寫
    • 查找

      • index(str,substr) 在目標字符串中查找子串的位置, 若返回 0 則表示不存在.
      • match(string, regexp, array) 字符串正則匹配, 將匹配結果保存在 arr 數組中.

        變量 RLENGTH 表示 match 函數匹配的字符串長度.

        變量 RSTART 表示 match 函數匹配的字符串的第一個字符的位置.

        awk 'BEGIN { if (match("One Two Three", "re")) { print RLENGTH } }'        # 輸出 2
        
        awk 'BEGIN { if (match("One Two Three", "Thre")) { print RSTART } }'    # 輸出 9
        cat test
        # this is wang,not wan
        # that is chen,not che
        # this is chen,and wang,not wan che
        
        awk '{match($0, /.+is([^,]+).+not(.+)/, a); print a[1],a[2]}' test
        # wang  wan
        # chen  che
        # chen  wan che
    • 替換

      • gsub(regx,sub [,targe=$0]) 全局替換, 會直接修改原始字符串, 返回替換成功的次數.

        如果 target 使用 $0, $... 等, 那麼替換成功後會使用 OFS 重新計算 $0

        這邊 sub 不支持反向引用, 只能使用 & 來引用匹配成功的部分

      • sub(regx,sub [,targe=$0]) 只替換第一個匹配的, 會直接修改原始字符串, 返回替換成功的次數.
      • gensub(regx, sub [, how [, target]]) 不修改原字符串, 而是返回替換後的字符串. 可以完全替代 gsubsub

        how: 指定替換第幾個匹配, 比如 1 表示只替換第一個匹配, gG 表示全局替換

        這是 gawk 提供的函數, 其中 sub 支持使用 \N 引用分組匹配, 或 &, \0 來標識匹配的整個結果.

        awk 'BEGIN {
            a = "111 222"
            b = gensub(/(.+) (.+)/, "\\2 \\1, \\0, &", "g", a)
            print b
        }'
        
        
        # 輸出
        222 111, 111 222, 111 222
    • 截取

      • substr(str,pos,num=剩餘所有) 從指定位置開始截取一個子串
    • 分割

      • split(str, arr [, 正則分隔符=FS]) 字符串分割為數組, 並將其保存到第2個參數中, 函數返回值是分割的數
      • patsplit(str, arr[, 正則分隔符=FPAT]) 使用正則捕獲匹配的字符串, 並將其保存到第2個參數中.

    若不清楚的, 可以在 man awk 中搜索相應關鍵字

    時間函數
    • systime 返回當前時間戳(秒級)
    • mktime("YYYY MM DD HH mm SS [DST]") 根據給定的字符串格式, 返回其對應的時間戳

      # 格式: 年 月 日 時 分 秒
      awk 'BEGIN{print mktime("2020 10 10 17 51 59")}'
    • strftime([format [, timestamp[, utc-flag]]]) 將時間戳(默認是當前時間)轉換為字符串表示

      awk 'BEGIN {print strftime("Time = %Y-%m-%d %H:%M:%S")}'
      
      輸出
      
      Time = 2020-10-12 17:53:37
      SN 描述
      %a 星期縮寫(Mon-Sun)。
      %A 星期全稱(Monday-Sunday)。
      %b 月份縮寫(Jan)。
      %B 月份全稱(January)。
      %c 本地日期與時間。
      %C 年份中的世紀部分,其值為年份整除100。
      %d 十進制日期(01-31)
      %D 等價於 %m/%d/%y.
      %e 日期,如果只有一位數字則用空格補齊
      %F 等價於 %Y-%m-%d,這也是 ISO 8601 標準日期格式。
      %g ISO8610 標準周所在的年份模除 100(00-99)。比如,1993 年 1 月 1 日屬於 1992 年的第 53 周。所以,雖然它是 1993 年第 1 天,但是其 ISO8601 標準周所在年份卻是 1992。同樣,儘管 1973 年 12 月 31 日屬於 1973 年但是它卻屬於 1994 年的第一週。所以 1973 年 12 月 31 日的 ISO8610 標準周所在的年是 1974 而不是 1973。
      %G ISO 標準周所在年份的全稱。
      %h 等價於 %b.
      %H 用十進制表示的 24 小時格式的小時(00-23)
      %I 用十進制表示的 12 小時格式的小時(00-12)
      %j 一年中的第幾天(001-366)
      %m 月份(01-12)
      %M 分鐘數(00-59)
      %n 換行符 (ASCII LF)
      %p 十二進制表示法(AM/PM)
      %r 十二進制表示法的時間(等價於 %I:%M:%S %p)。
      %R 等價於 %H:%M。
      %S 時間的秒數值(00-60)
      %t 製表符 (tab)
      %T 等價於 %H:%M:%S。
      %u 以數字表示的星期(1-7),1 表示星期一。
      %U 一年中的第幾個星期(第一個星期天作為第一週的開始),00-53
      %V 一年中的第幾個星期(第一個星期一作為第一週的開始),01-53。
      %w 以數字表示的星期(0-6),0表示星期日 。
      %W 十進制表示的一年中的第幾個星期(第一個星期一作為第一週的開始),00-53。
      %x 本地日期表示
      %X 本地時間表示
      %y 年份模除 100。
      %Y 十進制表示的完整年份。
      %z 時區,表示格式為+HHMM(例如,格式要求生成的 RFC 822或者 RFC 1036 時間頭)
      %Z 時區名稱或縮寫,如果時區待定則無輸出。
    其他函數
    • getline

      請參照下方的 "getline" 部分.

    • close(xxx [, from|to])

      關閉文件、shell進程, 可以僅關閉某一端.

      close(xxx, "to") 表示關閉該管道的寫入端, close(xxx, "from") 表示關閉該管道的輸出端.

      注意!!!! awk 中任何文件都只會在第一次使用時打開, 之後都不會再重新打開(而是從上次的讀取位置繼續). 因此只有在關閉之後, 再次使用時才會重新打開.

    • next 跳過對當前記錄的後續處理.

      會回到 awk 循環的頭部, 讀取下一行.

    • nextfile 停止處理當前文件, 從下一個文件開始處理.
    • return xx 函數返回值
    • system("shell命令") 執行 shell 命令, 並返回退出的狀態值, 0表示成功.
    • flush([output-expr]) 刷新打開文件或管道的緩衝區

      如果沒有提供 output-expr,fflush 將刷新標準輸出。若 output-epxr 是空字符串 (""),fflush 將刷新所有打開的文件和管道。

    • close(expr) 關閉文件句柄 ???

      awk 'BEGIN {
          cmd = "tr [a-z] [A-Z]"
          print "hello, world !!!" |& cmd        # "&|" 表示雙向管道通信
          close(cmd, "to")                    # 關閉其中一個方向的管道, 另一個是 "from" 方向
          cmd |& getline out                    # 使用 getline 函數將輸出存儲到 out 變量中
          print out;
          close(cmd);                            # 關閉管道
      }'
      
      輸出
      
      HELLO, WORLD !!!
    • exit <code=0> 終止腳本
    自定義函數
    function 函數名(參數) {
        awk 語句
        return awk變量
    }

    注意

    • 自定義函數的書寫不能再 BEGIN{}, {}, END{} 的裏層

    getline

    getline 函數用於讀取一行數據.

    根據不同情況, 返回值不一樣.

    • 若讀取到數據, 返回 1.
    • 若遇到 EOF, 返回 0.
    • 發生錯誤, 返回負數. 如-1表示文件無法打開,-2表示IO操作需要重試(retry)。在遇到錯誤的同時,還會設置 ERRNO 變量來描述錯誤.
    awk '{print $0; getline; print $0}' marks.txt 
    
    # 建議使用 getline 時判斷一下是否讀取成功
    awk 'BEGIN {getline; if ((getline) > 0) {...}}'
    從當前文件讀取
    • 使用 getline 不帶參數時, 表示從當前正在處理的文件中立即讀取下一條記錄並保存到 $0, 同時進行字段分割(分別存到 $1, $2,...), 同時會設置 NF, RT, NR, FNR, 然後繼續執行後續代碼.
    • 執行 getline <變量名> 時, 會將讀取結果保存到對應變量中, 而不會更新 $0, 也不會更新 NF, $1, $2, ..., 此時僅僅會更新 RT, NR, FNR.
    從其他文件讀取
    • 執行 getline < "filename" 表示從指定文件讀取一條記錄並保存到 $0 中(同時進行字段分割), 及 NF. 至於 NR, FNR 則不會更新.

      每次讀取記錄後會自動記錄讀取的位置.

      配合 whilegetline 的返回值可以遍歷完文件.

      注意 getline < abcgetline < "abc" 是兩碼事, 一個是從 abc 變量指向的文件讀取, 一個是讀取 abc 文件
    • 執行 getline 變量名 < "filename" 表示從指定文件讀取一條記錄並保存到指定變量中.
    從 shell 命令輸出結果中讀取
    • cmd | getline:從Shell命令 cmd 的輸出結果中讀取一條記錄保存到 $0

      會進行字段劃分,設置變量 $0 NF $N RT,不會修改變量 NR FNR

    • cmd | getline var:從Shell命令 cmd 的輸出結果中讀取數據保存到 var

      除了 varRT,其它變量都不會設置

    如果要再次執行 cmd 並讀取其輸出數據,則需要close關閉該命令, 示例:。

    # 若屏蔽下方的 close 函數, 則再次讀取該 cmd 時為空. 因此需要關閉先.
    
    awk 'BEGIN {cmd = "seq 1 5"; \
    while((cmd | getline) > 0) {print}; \
    close(cmd); \
    while((cmd | getline) > 0) {print}; \
    }'

    可以方便地使用 shell 給 awk 中變量賦值

    awk 'BEGIN {get_date = "date +\"%F %T\"";  \
    get_date | getline cur_date; \
    print cur_date; \
    close(get_date);
    }'
    
    輸出
    2020-10-13 19:14:17
    將數據傳給 shell 處理完(coprocess), 再讀取

    這裏要利用 coprocess, 也就是 |& 操作符.

    awk 可以利用 |& 將一些不好處理的數據傳給 shell 來處理後, 再從 shell 中讀取結果, 繼續在 awk 中處理.

    使用 shell 的排序功能

    # sort 命令會等待數據寫入完畢後(即 EOF 標記)才開始排序, 因此實際是在執行 close(CMD, "to") 時才開始執行.
    awk 'BEGIN {
    CMD = "sort -k2n";
    print "6 66" |& CMD;
    print "3 33" |& CMD;
    print "7 77" |& CMD;
    close(CMD, "to");
    while ((CMD |& getline) > 0) {
        print;
    }
    close(CMD);
    }'
    
    
    輸出:
    3 33
    6 66
    7 77

    使用注意:

    • awk-print |& cmd 會直接將數據寫進管道, cmd 可以從管道中獲取數據
    • 強烈建議在awk_print寫完數據之後加上 close(cmd,"to") ,這樣表示向管道中寫入一個EOF標記,避免某些要求讀完所有數據再執行的cmd命令被永久阻塞.

      關閉管道另一端可以使用 close(cmd, "from")

    • 如果 cmd 是按塊緩衝的,則 getline 可能會陷入阻塞。這時可將 cmd 部分改寫成 stdbuf -oL cmd 以強制其按行緩衝輸出數據

      CMD="stdbuf -oL cmdline";awk_print |& CMD;close(CMD,"to");CMD |& getline

    高級輸出

    print 輸出重定向
    • print "..."
    • print "..." > 文件名 重定向輸出
    • print "..." >> 文件名 重定向輸出

      這玩意比使用 system 函數再調用 echo 快了好幾個量級
    • print "..." | "shell 命令" awk 將創建管道, 並啓動 shell 命令. print 產生的數據放入管道, shell 命令則從管道中讀取數據.
    • print "..." |& "shell 命令"; "shell 命令" | getline 和上面的 | 不同之處在於, 這裏是將數據交給 Coprocess, 之後 awk 還需要再從 Coprocess 取回數據.

      Coprocess 執行 shell 命令的時候, 結果是不輸出到標準輸出的, 而是需要從管道中自行讀取.

    支持重定向到

    • 標準輸入 /dev/stdin
    • 標準輸出 /dev/stdout
    • 標準錯誤 /dev/stderr

    若 print 輸出後發現後續的管道命令沒有內容, 那其實是因為 awk 的輸出存在緩存, 可使用 fflush() 函數刷新緩衝區.

    關於 ||& 使用上區別的示例

    # 這裏用的是 |, 執行結果直接輸出到標準輸出
    awk 'BEGIN {
    cmd = "tr \"[a-z]\" \"[A-Z]\""
    print "hello" | cmd
    > }'
    
    
    # 這裏用的是 |&, 執行結果需要手動從 Coprocess 管道讀取
    awk 'BEGIN {
        cmd = "tr \"[a-z]\" \"[A-Z]\""
        print "hello" |& cmd;
        close(cmd, "to");
        cmd |& getline line;
        print line;
        close(cmd);
    }'
    
    
    輸出
    HELLO
    printf 格式化
    printf(format, value1, value2, ...)
    
    參數
        format    格式  
            %c        (將ASCII轉換為)字符
            %s        字符串
            %d,%i    整數    
            %e,$e    科學計數法表示
            %f,%F    浮點數
            %g,%G    浮點數, 會移除對數值無影響的 0
            %o        無符號八進制
            %u        無符號十進制
            %x,%X    無符號十六進制
            %%        百分號
        
                        
    注意: 輸出時不會自動換行
    
        
    示例
        printf("total pay for %s is $%.2f\n", $1, $2 * $3)
    
        # %-8s        字符串, 8個字符寬度, 左對齊
        # %6.2f        浮點數, 6個字符寬度, 保留2位小數
        printf("%-8s %6.2f\n", $1, $2 * $3)
    參考: https://awk.readthedocs.io/en...

    若僅僅是為了格式化字符串(不輸出), 可以用 sprintf 函數.

    format 支持的轉義序列

    • 換行符 \n
    • 水平製表符 \t
    • 垂直製表符\v

      理解為輸出光標垂直向下移動一行
    • 退格符 \b

      理解為輸出光標後退一格.
    • 回車符 \r

      理解為光標會回退到當前行的開頭(常用於覆蓋本行)
    • 換頁符 \f

      這個效果...得試一下才行

    format 的 % 的可選參數

    • 寬度

      只有當字段的寬度比要求寬度小時該標示才會有效, 默認使用空格字符填充.

      printf("%10d", 1)
      
      
      輸出
               1
    • 前導零 0

      只有當字段的寬度比要求寬度小時該標示才會有效

      awk 'BEGIN {printf("%010d", 1)}'
      
      
      輸出
      0000000001
    • 左對齊 -

      % 和數字之間使用 - 符號即可指定左對齊

      awk 'BEGIN {printf("%-10d", 1)}'
      
      輸出
      1
    • 符號前綴 +

      使用 +, 在輸出數字時, 將其正負號也輸出.

      awk 'BEGIN {printf("%+10d", 1)}'
      
      輸出
              +1
    • 保留進制標識 #

      awk 'BEGIN {printf("%#o    %#X\n", 10, 10)}'
      
      輸出
      012    0XA

    Examples

    打印出當前的可用內核列表

    並顯示序號

    awk -F "'" '/^menuentry/ {print x++,$2}' /boot/grub2/grub.cfg
    
    輸出
    0 CentOS Linux (5.5.6) 7 (Core)
    1 CentOS Linux (3.10.0-1062.12.1.el7.x86_64) 7 (Core)
    2 CentOS Linux (3.10.0-957.el7.x86_64) 7 (Core)
    3 CentOS Linux (0-rescue-d64aa77b8f014365aa6557986697df9c) 7 (Core)

    統計當前tcp的各個狀態及數量

    netstat -ntp | awk '/^tcp / {S[$6]++} END{for (i in S) print i,S[i];}'
    
    輸出
    TIME_WAIT 108
    ESTABLISHED 154

    統計每個接口的訪問時間

    time cat 20_10_*.txt | grep "request cost" | gawk -v suffix=_test -f ../stat_method.awk

    stat_method.awk

    BEGIN {
        DEBUG = 1
    
        methodRecordFile = "method_record"suffix".csv"
        methodStatsFile = "method_stats"suffix".csv"
    
        if (DEBUG) {
            methodRecordFile = "/dev/stdout"
            methodStatsFile = "/dev/stdout"
        }
    
        print methodRecordFile
        print methodStatsFile
    }
    
    {
        method=substr($10,2,length($10)-2)
        method=substr(method,1,length(method)-7)
        timeStr=$1" "$2
        timeMs=$6 + 0.01
        uid=substr($11,2,length($11)-2)
    
        msLevel=(int(timeMs/100) + 1) * 100
    
        T[method][msLevel]++
    
        if (!(method in Stats)) {
            Stats[method]["name"] = method
            Stats[method]["max"] = 0        
            Stats[method]["mean"] = 0
            Stats[method]["count"] = 0
            Stats[method]["sum"] = 0
            Stats[method]["sumX2"] = 0
            Stats[method]["min"] = 9999999999999
        }
    
        if (timeMs > Stats[method]["max"]) {
            Stats[method]["max"] = timeMs
        }
    
        if (timeMs < Stats[method]["min"]) {
            Stats[method]["min"] = timeMs
        }
    
        Stats[method]["sumX2"] += timeMs * timeMs
    
        Stats[method]["count"]++
        Stats[method]["sum"] += timeMs
    
        if (NR % 10000 == 0) {
            print "已處理 "NR" 條記錄"
        }
    }
    
    END {
        print "----------- 總共處理 "NR" 條記錄 -----------"
    
    
        print "method,msLevel,count" > methodRecordFile
        # print "method,msLevel,count"
        for (m in T) {        
            for (l in T[m]) {
                print m","l","T[m][l] >> methodRecordFile
                # print m","l","T[m][l]
            }
        }
    
        print "----------- done -----------"
        
        print "method,min,max,mean,sd(標準差),count,sum(秒)" > methodStatsFile
        printf("%-30s\tmin\tmax\tcount\tmean\tsd(標準差)\t,sum(秒)\n", "method")    
        for (m in Stats) {
            Stats[m]["mean"] = Stats[m]["sum"] / Stats[m]["count"]
            Stats[m]["sd"] = sqrt(Stats[m]["sumX2"] / Stats[m]["count"] - Stats[m]["mean"] * Stats[m]["mean"])
    
            # for (n in Stats[m]) {
                # print "Stats["m"]["n"]= " Stats[m][n]
            # }
    
            print m "," Stats[m]["min"] "," Stats[m]["max"] "," Stats[m]["mean"] "," Stats[m]["sd"]"," Stats[m]["count"]","Stats[m]["sum"]/1000 >> methodStatsFile
            printf("%-30s\t%.2f\t%.2f\t%.0f\t%.2f\t%d\t%.2f\n", substr(m, 0, 30), Stats[m]["min"], Stats[m]["max"],Stats[m]["mean"],Stats[m]["sd"], Stats[m]["count"], Stats[m]["sum"]/1000)        
        }
    
        print "----------- done -----------"
        print methodRecordFile
        print methodStatsFile
    } 

    格式化空白

    cat > a.txt <<'EOF'
          aaaa        bbb     ccc
       bbb     aaa ccc
    ddd       fff             eee gg hh ii jj
    EOF
    
    awk 'BEGIN{OFS=" "} {NF=NF; print}' a.txt

    輸出

    aaaa bbb ccc
    bbb aaa ccc
    ddd fff eee gg hh ii jj

    打印 ini 文件中的某一段

    awk -v scope="extras" 'BEGIN {scope="["scope"]"} 
    $0 == scope {
        print;
        while ((getline) > 0) {
          if ($0 !~ /\[.*\]/) { print $0; } else { exit }
        }
    }
    ' /etc/yum.repos.d/CentOS-Base.repo

    輸出如下 👇

    [extras]
    name=CentOS-$releasever - Extras
    mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras&infra=$infra
    #baseurl=http://mirror.centos.org/centos/$releasever/extras/$basearch/
    gpgcheck=1
    gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
    
    #additional packages that extend functionality of existing packages

    處理字段中包含字段分割符情況(csv)

    echo 'Robbins,Arnold,"1234 A Pretty Street, NE","MyTown",MyState,12345-6789,USA' | awk 'BEGIN { FPAT="[^,]+|\"[^\"]+\"" } { print NF"    "$3}'

    輸出如下 👇

    7    "1234 A Pretty Street, NE"
    user avatar
    0 位用戶收藏了這個故事!

    發佈 評論

    Some HTML is okay.