strace 与 打印行号

文件大小基础知识

给定字符串:line_0001,共 9 个字符,9 字节

expr length line_0001

9

给定空文本文件:touch test.txt

du -sb test.txt

0       test.txt

将字符串写入空文本文件中,有 2 种情景

# 不加换行符,文件大小为 9 字节
echo -n line_0001 > test.txt

# 加换行符,文件大小为 10 字节
echo -n line_0001 > test.txt

可知换行符\n只占用 1 个字节,du 命令获取到的文件大小就是文件内容本身的字节数总和,并没有包含 fs 的元数据

文件读取基础知识

将 9 个字符写入空文本文件中,加上换行符,文件大小为 10 字节

echo line_0001 > test.txt

使用 strace 来跟踪 cat 命令系统调用和信号

strace -v cat test.txt 1>/dev/null

  • -v:打印完成的系统调用信息,默认会进行缩减,以缩短每行的内容
  • 1>/dev/null:把标准输出的内容扔掉,避免干扰阅读 strace 的系统调用信息
# 打开文件
open("test.txt", O_RDONLY)              = 3

# 获取文件状态
fstat(3, {st_dev=makedev(8, 4), st_ino=4980738, st_mode=S_IFREG|0644, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=8, st_size=10, st_atime=2021/09/28-15:47:48, st_mtime=2021/09/28-16:43:39, st_ctime=2021/09/28-16:43:39}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0

# 第一次读取文件内容,buffer 为 65535 字节,即每次最多从文件读取的数据量;这里只读到 10 字节的内容
read(3, "line_no_0001\n", 65536)        = 10

# 调用 write 把数据写到信号为 1 的进程,即把内容输出到标准输出
write(1, "line_0001\n", 13)          = 10

# 第二次读取文件内容,为空,所以就结束了 read 这个系统调用
read(3, "", 65536)                      = 0
close(3)                                = 0

# 结束信号为 1 的系统调用
close(1)                                = 0

打印指定行号内容

生成文本文件

> test.txt; for i in {0001..6554}; do echo "line_$i" >> test.txt; done

line_0001
line_0002
line_0003
...
line_6552
line_6553
line_6554

查看文件状态

stat test.txt

File: ‘test.txt’
  Size: 65540           Blocks: 136        IO Block: 4096   regular file
Device: 804h/2052d      Inode: 4980738     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2021-09-28 15:47:48.825610568 +0800
Modify: 2021-09-28 19:47:05.209576214 +0800
Change: 2021-09-28 19:47:05.209576214 +0800
 Birth: -
item desc
Size 每行内容长度:65540/6554 = 10 字节
Blocks 理论上只需要 2 个 block 即可存储整个文件的内容,但实际上数据存放在 136 个</br> block 上了
Inode 4980738 是该文件的入口 inode id

方式一:使用 sed 命令打印 405-415 行的内容

sed:stream editor

sed -n '405,415p' test.txt

line_0405
line_0406
line_0407
line_0408
line_0409
line_0410
line_0411
line_0412
line_0413
line_0414
line_0415

目标已达成,使用 strace 跟踪一下 sed 的操作

strace -vy sed -n '405,415p' test.txt 1>/dev/null

# 因为是 ssh 登陆但,所以是通过伪终端 /dev/pts/1 输入命令,后面会有一个 close pts 设备的动作
execve("/usr/bin/sed", ["sed", "-n", "405,415p", "test.txt"], ..., "SSH_TTY=/dev/pts/1", ...) = 0
...

# 打开文本文件 test.txt,通过 man open 得知函数的定义为:
#    int open(const char *pathname, int flags);
#    参数 flags 就是 access modes: O_RDONLY, O_WRONLY, or O_RDWR 
open("test.txt", O_RDONLY)              = 3

# 获取文件的状态,数据存放在 136 个 block 中,获取的文件信息和 stat 命令的一致
#    通过 man fstat 得知,fstat 完全等同于 stat,数据结构如下:
#    struct stat {
#        dev_t     st_dev;     /* ID of device containing file */
#        ino_t     st_ino;     /* inode number */
#        mode_t    st_mode;    /* protection */
#        nlink_t   st_nlink;   /* number of hard links */
#        uid_t     st_uid;     /* user ID of owner */
#        gid_t     st_gid;     /* group ID of owner */
#        dev_t     st_rdev;    /* device ID (if special file) */
#        off_t     st_size;    /* total size, in bytes */
#        blksize_t st_blksize; /* blocksize for file system I/O */
#        blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
#        time_t    st_atime;   /* time of last access */
#        time_t    st_mtime;   /* time of last modification */
#        time_t    st_ctime;   /* time of last status change */
#    };
fstat(3</data/masoncheng/tmp/test.txt>, {st_dev=makedev(8, 4), st_ino=4980738, st_mode=S_IFREG|0644, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=136, st_size=65540, st_atime=2021/09/28-15:47:48, st_mtime=2021/09/29-14:47:01, st_ctime=2021/09/29-14:47:01}) = 0

# 为 test.txt 创建 mmap 的内存映射地址:
#    mmap:将文件映射到内存地址,读取文件时直接通过内存地址读取,绕过磁盘寻址,提升读性能。通过命令 man mmap 得知函数定义为:
#       void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
#    sed 读文件前,都先用 mmap 把文件映射到内存,cat 没有 mmap 操作,理论上 sed 的读取性能比 cat 更好,实则不然
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2e54258000

# 第一次 read:
#    read 的 buffer 根据 os 的 block size 设置,默认为 4096 字节,每次最多读取 4K 个的字符,减少读取次数
#    每次 read 的数据是 4096 bytes,那么共读取:16 + 1 次(65540 = 16*4096 + 4)才能读完数据,读完后,还会额外读取一次,最后一次为读到的数据为空,这时才结束读取操作
read(3</data/masoncheng/tmp/test.txt>, "line_0001\nline_0002\nline_0003\nli"..., 4096) = 4096

# 因为要将指定行数的数据输出到 /dev/null,所以不管第一次 read 是否有输出,都要要获取 /dev/null 设备的文件信息;
# 这里仅获取设备信息,不做任何输出,要等 sed 遍历完整个文件才输出,这个点需要优化
fstat(1</dev/null>, {st_dev=makedev(0, 5), st_ino=674671422, st_mode=S_IFCHR|0666, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=0, st_rdev=makedev(1, 3), st_atime=2018/08/20-11:39:20, st_mtime=2018/08/20-11:39:20, st_ctime=2018/08/20-11:39:20}) = 0

# 通过命令 man ioctl 发现,这是个用来控制设备的函数,函数定义为:int ioctl(int d, int request, ...);
ioctl(1</dev/null>, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffec3c33a60) = -1 EACCES (Permission denied)

# mmap 映射 /dev/null 设备到内存中
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2e54257000

# 第 2-16 次 read
read(3</data/masoncheng/tmp/test.txt>, "410\nline_0411\nline_0412\nline_041"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "ne_0820\nline_0821\nline_0822\nline"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "9\nline_1230\nline_1231\nline_1232\n"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "_1639\nline_1640\nline_1641\nline_1"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "line_2049\nline_2050\nline_2051\nli"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "458\nline_2459\nline_2460\nline_246"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "ne_2868\nline_2869\nline_2870\nline"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "7\nline_3278\nline_3279\nline_3280\n"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "_3687\nline_3688\nline_3689\nline_3"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "line_4097\nline_4098\nline_4099\nli"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "506\nline_4507\nline_4508\nline_450"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "ne_4916\nline_4917\nline_4918\nline"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "5\nline_5326\nline_5327\nline_5328\n"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "_5735\nline_5736\nline_5737\nline_5"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "line_6145\nline_6146\nline_6147\nli"..., 4096) = 4096
read(3</data/masoncheng/tmp/test.txt>, "554\n", 4096) = 4
# 最后一次 read,读到的内容为空
read(3</data/masoncheng/tmp/test.txt>, "", 4096) = 0

# 关闭 test.txt 文件
close(3</data/masoncheng/tmp/test.txt>) = 0

# 删除 test.txt 文件的 mmap 映射
munmap(0x7f2e54258000, 4096)            = 0

# 将缓存中的数据写入到 /dev/null 设备
write(1</dev/null>, "line_0405\nline_0406\nline_0407\nli"..., 110) = 110
# 关闭 /dev/null 设备的文件描述符
close(1</dev/null>)                     = 0

# 删除 /dev/null 设备的 mmap 映射
munmap(0x7f2e54257000, 4096)            = 0

# 关闭 /dev/pts/1 伪终端文件,表示字符界面的字符输入已完成
# 通过 man pts 发现,这里获取了一对伪终端的主备设备信息(伪终端就是我们平常常用的 xterm、minicom 等字符终端):
#    /dev/ptmx:特殊字符设备,伪终端的 master 只有 1 个
#    /dev/pts/1:特殊字符设备,伪终端的 slave,slave 有很多个
close(2</dev/pts/1>)                    = 0

exit_group(0)                           = ?

octl(1</dev/null>, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffec3c33a60) = -1 EACCES (Permission denied)

# 命令执行成功,返回状态码 0
+++ exited with 0 +++

优化一下,在打印完指定行后就退出,不再继续读取下去,再对比下 strace 信息

strace -vy sed -n '405,415p;415q' test.txt 1>/dev/null

open("test.txt", O_RDONLY)              = 3
fstat(3</data/masoncheng/tmp/test.txt>, {st_dev=makedev(8, 4), st_ino=4980738, st_mode=S_IFREG|0644, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=136, st_size=65540, st_atime=2021/09/28-15:47:48, st_mtime=2021/09/29-16:48:58, st_ctime=2021/09/29-16:48:58}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3825a5c000

# 第一次读
read(3</data/masoncheng/tmp/test.txt>, "line_0001\nline_0002\nline_0003\nli"..., 4096) = 4096

fstat(1</dev/null>, {st_dev=makedev(0, 5), st_ino=674671422, st_mode=S_IFCHR|0666, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=0, st_rdev=makedev(1, 3), st_atime=2018/08/20-11:39:20, st_mtime=2018/08/20-11:39:20, st_ctime=2018/08/20-11:39:20}) = 0
ioctl(1</dev/null>, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7ffe73911900) = -1 EACCES (Permission denied)
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3825a5b000

# 第二次读
read(3</data/masoncheng/tmp/test.txt>, "410\nline_0411\nline_0412\nline_041"..., 4096) = 4096

close(3</data/masoncheng/tmp/test.txt>) = 0
munmap(0x7f3825a5c000, 4096)            = 0
write(1</dev/null>, "line_0405\nline_0406\nline_0407\nli"..., 110) = 110
close(1</dev/null>)                     = 0
munmap(0x7f3825a5b000, 4096)            = 0
close(2</dev/pts/60>)                   = 0
exit_group(0)                           = ?
+++ exited with 0 +++

可以发现,read 只执行 2 次:

  • 第一个 4096 字节的内容,只到读到 4096/10 = 409 行(409 行的内容只读到 2 个字符)
  • 第二个 4096 字节的内容,读到 8192/10 = 819 行(819 行的内容读到 8 个字符)

方式二:使用 awk 打印 405-415 行的内容

命令:

awk '(NR>=405 && NR <= 415) {print}' test.txt

使用 strace 发现,和 sed 一样,打印完指定行内容后也是继续读取剩余内容,这里也同样可以做优化:

awk '(NR>=405 && NR <= 415) {print; exit}' test.txt

awk 的 read buffer 也是使用 os 的 block size

更多用法

参考:https://www.cnblogs.com/daodaotest/p/13277208.html

生成测试文本内容

seq -f "%02g daodaotest" 1 10 > test.txt

查看测试文本内容,并显示行号

cat -n test.txt

     1  01 daodaotest
     2  02 daodaotest
     3  03 daodaotest
     4  04 daodaotest
     5  05 daodaotest
     6  06 daodaotest
     7  07 daodaotest
     8  08 daodaotest
     9  09 daodaotest
    10  10 daodaotest

awk '{print NR" "$0}' test.txt

1 01 daodaotest
2 02 daodaotest
3 03 daodaotest
4 04 daodaotest
5 05 daodaotest
6 06 daodaotest
7 07 daodaotest
8 08 daodaotest
9 09 daodaotest
10 10 daodaotest

打印前 5 行内容

head -n 5 test.txt
sed -n '1,5p' test.txt
awk 'NR<6' test.txt

打印第 5 行内容

sed -n '5p' test.txt
awk 'NR==5' test.txt
tail -n +5 test.txt | head -1

打印 5~10 行内容

sed -n '5,10p' test.txt
awk 'NR>4 && NR<11' test.txt
tail -n +5 test.txt | head -6

打印第 3 行 和 5~7 行内容

sed -n '3p;5,7p' test.txt
awk 'NR==3 || (NR>4 && NR<8)' test.txt

打印奇偶行内容

# NR 表示行号
awk 'NR%2!=0' test.txt
awk 'NR%2' test.txt

## i 为变量,未定义变量初始值为 0,对于字符运算,未定义变量初值为空字符串
## 读取第 1 行记录,进行模式匹配:i=!0(!表示取反)。! 右边是个布尔值,0 为假,非 0 为真,!0 就是真,因此 i=1,条件为真打印第一条记录。
## 读取第 2 行记录,进行模式匹配:i=!1(因为上次 i 的值由 0 变成了 1),条件为假不打印。
## 读取第 3 行记录,因为上次条件为假,i 恢复初值为 0,继续打印。以此类推...
## 上述运算并没有真正的判断记录,而是布尔值真假判断。
$ awk 'i=!i' test.txt

## m~np:m 表示起始行;~2 表示:步长
$ sed -n '1~2p' test.txt

## 先打印第 1 行,执行 n 命令读取当前行的下一行,放到模式空间,后面再没有打印模式空间行操作,所以只保存不打印,同等方式继续打印第 3 行。
$ sed -n '1,$p;n' test.txt
$ sed -n 'p;n' test.txt

# 打印偶数行内容
$ awk 'NR%2==0' test.txt
$ awk '!(NR%2)' test.txt
$ awk '!(i=!i)' test.txt
$ sed -n 'n;p' test.txt
$ sed -n '1~1p' test.txt
$ sed -n '1,$n;p' test.txt

打印后 5 行内容

tail -n 5 test.txt

打印最后一行内容

tail -n 1 test.txt
sed -n '$p' test.txt
awk 'END {print}' test.txt

打印以 "1" 开头的行内容

sed -n '/^1/p' test.txt
grep "^1" test.txt

打印不以 "1" 开头的行内容

$ sed -n '/1/!p' test.txt
$ grep -v "^1" test.txt

从匹配 "03" 行到第 5 行内容

sed -n '/03/,5p' test.txt

打印匹配 "03" 行 到匹配 "05" 行内容

sed -n '/03/,/05/p' test.txt
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • Linux 简单命令 1、ls 命令 就是 list 的缩写,通过 ls 命令不仅可以查看 linux 文件夹包含...
    b9d2d6e91b27阅读 395评论 0 0
  • 1、ls命令 就是list的缩写,通过ls 命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包...
    不倒翁的归宿阅读 222评论 0 0
  • 原创:熊+jimmy+SX 生信技能树 主要是awk/grep/sed这三驾马车,加上vi这个神器,最后辅助一些小...
    超级无敌大蜗牛阅读 1,646评论 0 6
  • 1、ls命令 就是 list 的缩写,通过 ls 命令不仅可以查看 linux 文件夹包含的文件,而且可以查看文件...
    gaoaoaoao阅读 378评论 0 1
  • 1、sed 是什么sed是stream editor的简称,也就是流编辑器。它一次处理一行内容,处理时,把当前处理...
    probie_rise阅读 264评论 0 0