Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2315168
  • 博文数量: 527
  • 博客积分: 10343
  • 博客等级: 上将
  • 技术积分: 5565
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-26 23:05
文章分类

全部博文(527)

文章存档

2014年(4)

2012年(13)

2011年(19)

2010年(91)

2009年(136)

2008年(142)

2007年(80)

2006年(29)

2005年(13)

我的朋友

分类: LINUX

2007-03-02 15:39:42

sed tricks and pitfalls

by Zhao rufei
2007-02-05

表示地址时只能用//来分隔pattern, 而在s命令中可以用任何东西来作分隔符

  • 由 于vi/vim中无论是地址还是substitute命令中都可以用非字母数字的字符作分隔符, perl中也广泛地支持不同的分隔符, 甚至支持如(){}<>[]这样的字符来分隔正则表达式的不同部分. 所以一个象我这么博学的人很容易就会智者千虑必有一失, 比如对于下面的任务, 我不假思索地给出了方案:
    11111

    dsfdsf
    sdfdsf
    sdfd

    22222

    kalsdfja;sdf
    jask;dflasdf

    sed -n '##,##p' filename
    然而, 输出是空!
    原因就是#被解释为了sed脚本的注释符号, 因为它是注释符号所有没有报错. 但对于
    sed -n '!!,!!p' filename
    呢? !不是注释符, 可恶的是它竟然也没有报错, 就是闷声不响地不干活.

w 的特殊性

  • 在遇到换行符之前不能再跟其它命令, 即使你用;进行分隔
  • 在同一个脚本中多次写同一个文件, 结果是未定义的 起初我只是发现了在混合了命令行上的重定向和脚本中写同一个文件时会输出混乱的结果.
    后来证实仅仅是在脚本内部混乱也仍然会发生.

label 可以出现在任何语句的前面

这里的任何可能会使它能出现的合法位置让你大吃一惊
  • 它可以出现在一个组合命令的子命令之前
  • 它可以是一个空命令
  • 它可以出现在那个进行匹配的地址之前
  • 实质上, 它几乎是C语言里面的标号
  • /h
    s#.*#DEBUG: tablespace: &#p
    g
    #在{}括起来的子命令之前.

    :next N
    =
    p
    b pair
    }
    #但断然不可能放在s命令的flags部分, 也叫s的参数, s的参数只能一个紧挨一个, 不能象{}分组的子命令那样用;隔开

    s#$#___#p
    b end

    #最规矩的位置, 在一个命令之前

    :pair /\n<\/table/{
    s#\n#|#gp
    b end
    }

    b next
    #为了彰显它是一个空语句, 我故意在其前后放上了分号

    ;:end;

另外, 标号最长7个字符

b和t: label能合法定义在哪里, 它们就能跳到哪里, 就象C语言里面下面的代码一样丑陋

goto inside_a_block;
....
if( condition )
{
...
inside_a_block:
...
}

\n可以出现吗? 它代表什么?

s#part1#part2#命令中, \n出现在part1部分可以匹配除最后一个换行符之外的其它换行符, 换句话说, 它匹配那些embedded的换行符. 比如你用

echo $'abc\n123\nxyz' | sed '/abc/{N;s#\n##g;}'
可以把abc那行跟123那行给连接起来, 原因是abc行尾的\n被替换为了"空", 尽管s命令有g这个参数, sed也不允许\n匹配到123这一行的行尾换行符, s命令发生时在待匹配的内部缓冲区中存放的内容是
abc
123
而替换之后的结果是
abc123

出现在part2部分的\n总是会被替换成一个新行, 一个换行, 或者说\n, ASCII(10), 是的, 我知道有些人做过:

sed 's#^M##g' a_ms_dos_crlf_file
不奇怪, 我也这么干过, 在bash命令行上, 那个^M是通过先按下CTRL-V, 然后敲回车键得到的, 在从键盘读入时, 它是三个键, 两个击键动作, 你的终端驱动程序把这个过程的结果转换成了一个字符, 它不是\n, 而是\r, ASCII为13. 回车和换行是不同的.

有个跟s平级的p命令, s命令还有个参数/flags也是p

从sed的命令构成来看, s命令的p参数是个多余之物, 因为

s#src#dest#gp
完全可以通过
s#src#dest#g
p
两个命令来实现, 在命令行上, 也可以通过
s#src#dest#g;p
来让它更紧凑一点

我猜sed的设计者注意到人们往往希望在修改了一样东西之后看看它的结果. 所以p

有个跟s平级跟p相关的=命令, s命令没有等价之物

  • =命令只是输出当前行号
  • 命令行上, 一旦你用{}来分组命令, 除了最后一个命令, 每个命令后面 必需有一个;分隔符
  • 作为命令的分隔符, 不论是全局的sed命令还是框在{}中的子命令, 换行符和;总是可以互相代替, 了解了这一点, 再看看对{}的说明

    • {之后必需是换行符
    • {必需独占一行
    • 注意后面不要留有空白字符
    这样看来, 允许你在命令行上写下面的语句已经算是客气的了:
    sed -n ' { p ;p  } '
    这里你可以在{}的之前和之后都加上空格. 最后一个p还可以没有;号

#是注释符, 但在s命令中它可以作为分隔符, 在有些sed实现中它不必出现在行首

调用技巧--printf

printf是占据着最调用市场的大量份额, 如果一段sed脚本或一个sed命令行不工作, 怎样知道它有没有匹配到你指定的某个模式, 下面是如何在sed中实现printf:

echo $'abc\nxyz' | sed '/abc/{h;s/abc/DEBUG: [&] matched/p;g}'
这个技巧是通过 s命令的p参数来输出调试信息, 因为s命令总是操纵缓冲区, 所以如果你想让你的调试信息独立于pattern 空间中要输出的东西, 可以象这样, 先用h把它暂存, 注意暂存不是move, 而是copy, 所以h命令之后pattern 空间中的内容还在, 你也可以对它执行操作, 修改它的内容是你能输出调试信息的关键, 最后的g把保存起来的pattern空间的内容恢复

能把调试信息输出到stderr而不是stdout吗, 至少我不能!

调试技巧--=

=输出行号, 它很简单, 但有用, =号用来跟踪你当前处理到了哪些行, 它跟p命令并不重复, 有时候你用了太多的N命令, 或者原始的输入中有太多重复的行, 或者仅仅只是内容太多时, p命令的输出会让你迷失方向

奇技淫巧--合并某个范围的行

象开头的文本那样, 那是我写这篇东西的原始动因

11111

dsfdsf
sdfdsf
sdfd

22222

kalsdfja;sdf
jask;dflasdf

原始的需求只是输出之间的行, 我把它推进了一步, 如何让这些行连接成一行, 就象vim的j命令所做的那样(vim的j命令默认情况下连接的两行之间有一个空格, j!可以去掉这个空格) 下面是我得到的最终方案:
 sed -n '/    
这绝不是一笔挥就的结果. 之前我几乎没用过label, 而且认为它基本上没什么用. 让我找个放大镜, 把上面的代码放大:
/#我自鸣得意的printf
h
s#.*#DEBUG: tablespace: &#p
g
#只要没遇到
:next N
#看看我处理到了第几行
=
#看看当前的pattern空间中都读进来了什么
p
#每读一行都要检查是不是遇到了结束的
b pair
}
#如果当前行不是处于之间
#就会跑到这里, 这一行实际上是个短路操作, 直接处理下一行了
b end

#检查当前pattern 空间中有没有读入一个
开头的行
:pair /\n<\/table/{
#如果有, 整个地址空间的内容都已经被完整读到了pattern space中
#去除行间的换行符
s#\n#|#gp
#至此, 这个"开始"..."结束"的块已经处理完毕, 继续下一行
b end
}
#如果自从最近一次已经匹配到以来还没有碰到
#的话, 会到这里, 直接跳去读下一行
b next
#为了彰显它是一个空语句, 我故意在其前后放上了分号
:end
下面是它的伪代码
while( 从输入中读取一行 )
{
if( 匹配到开始 )
{
label_读取下一行:
读取下一行
goto label_检查是否已经读取到了结束的块
}
else
{
break;
}
label_检查是否已经读取到了结束的块:
if( 匹配到结束 )
{
将所有读入的行连接成一行, 输出
break;
}
else
{
goto label_读取下一行
}
}
label_检查是否已经读取到了结束的块:
其中 label_检查是否已经读取到了结束的块: 和相关的goto语句是不必要的, 但在 sed中必需要通过跳转来实现这种分支结构.

奇技淫巧--自制T型管

tee 命令来自T型管的思想, 一处输入, 两处输出. 对于输出过长的命令尤其有用, 既可以通过屏幕输出让你了解当前程序的执行情况, 知道程序并没死掉. 又可以把结果存成文件留待分析. 最有用的用法其实是

command 2>&1 | tee result.txt

unix中当前tee也是很容易得到的, 但一旦没有呢? sed的w命令也可堪此任:

command 2>&1 | sed 'w result.txt'

就这么简单, sed默认对于每个pattern空间中的内容是会输出的, 所以它会向屏幕上输出, 同时w result.txt命令又会输出到result.txt中.

再进一步, 如果屏幕上输出内容太多, 而且你是通过telnet在操作一个速度很慢的远程机器, 如之奈何? 最好的办法是把整个结果都能存成文件, 同时又在屏幕上间隔性的输出, 比如, 每隔100行输出一次.

command 2>&1 | sed -n '1~100p; w result.txt'
-n 把默认的每行输出关闭, 而通过1~100p中的命令p显式决定何时输出. 想试一下功效如何不必手工生成100行的文件:
seq 1 100 | sed -n '1~10p; w result.txt'

当然, 用sed实现tee的缺点是文件不能被追加, 只能被改写, 不过这似乎不是一个什么困扰.

奇技淫巧--查看特殊字符的内码

比如你想知道某个汉字的UTF-8内码表示和GB2312内码表示, 假设你的终端当前配置成utf-8的

echo -n 中国 | sed l
那是小写的L, 不是数字1, 它的作用是输出非可见字符的内码, 你得到的是八进制, 不过变成16进制也决非难事:
for i in $(echo 中国 | sed l | sed -n '1{s#.$##;s#\\# 0#gp}');do printf "%0#x\n" $i; done
当然, 如果系统里有xxd 的话, 这些显得太舍近求远了:
echo 中国 | xxd -g1
xxd是vim附带的一个小工具. 在Redhat Linux上, 所谓的vi正是vim. 所以xxd 还算是可以依靠的工具.

奇技淫巧--查看一个很大的文件中的某一行

head -100000 large_file | tail -1
用这个查看一个很大的文件的第10万行当然很好, 因为head会在读到这一行时停止. 但还有更好的:
sed -n '100000{p;q}' large_file
它更好是因为通过 q避免了对整个文件后续行的不必要的处理, sed进程马上退出, 同时又避免了管道和另一个进程的开销.
阅读(1113) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~