V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ysmood
V2EX  ›  程序员

如何脱离 grep, sed, awk 完成一些批量任务?

  •  3
     
  •   ysmood ·
    ysmood · 2015-01-11 11:38:38 +08:00 · 5707 次点击
    这是一个创建于 3611 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如何脱离 grepsedawk 完成一些批量任务?

    首先要提下 python 的 -c 选项,比如打印 10python -c 'print 10' 。即使不用 -c 选项,用 pipe 也是可以的,如 echo 'print 10' | python。这种用法非常标准,ruby,lua,node 之类的一般解释器都支持。ruby 甚至支持 -n-p 这种便利的选项。

    一旦你理解了 pipe 的基本原理:

    1. 你可以用 cat | python,输入完 print 10enter, ctrl-D 结束输入。这样做的好处是可以随意打回车或者引号了。

    2. 如果你会 vim,可以开空 vim,然后 insert 模式下输入 print 10,然后命令模式下输入 :w !python 结束输入。用 vim 的好处是有语法高亮等高级功能而不产生临时文件。

    Ruby

    这里先讲 ruby, 因为 mac 自带 ruby,它原 生字符串处理库 很强大,而且有便利的撇号 "`" 和 "%x",感兴趣的话可以看看文档,即使你不懂 ruby 也会觉得这些让它非常适合处理 cli 任务。

    为了便于输入,系统里加了几个 alias:

    alias r_init="ruby -e 'require \"FileUtils\"; F, D = FileUtils, Dir'"
    alias r="r_init -e"
    alias rp="r_init -p -e"
    
    1. 打印第一个含有数字的文件名

      bash: ls -1 | grep -m 1 -e '.*[0-9].*'

      ruby: r 'puts `ls`[/.*\d.*/]' (少打 9 字符)

    2. 替换所有文件名中的 y 为 30 个 o

      bash: ls -1 | sed s/y/oooooooooooooooooooooooooooooo/

      ruby: ls -1 | rp '$_.gsub! /y/, "o" * 30' (少打打 12 字符)

      你还可以继续把这个命令 pipe 下去。

    3. 批量重命名文件为 “[3 字宽 0 补齐递增]” + “原文件名” 的形式

      bash: 这个 sed 我不好好查查文档也写不出 ;P

      ruby: r 'D["*"].each_with_index { |f, i| F.mv f, "[%03d]" % i + f }'

      操作类似如下命令:

      mv \"a\".txt \[001\]\"a\".txt
      mv a\ b.txt \[002\]a\ b.txt
      

      文件名中可能有引号或空格,使用 sed 拼接 mv 命令时需注意转义,而这里 ruby 调用的原生方法,所以无需转义即是安全的。

      不借助三方库,其他语言很难写的如此之短,且易于理解和记忆。

    Node & Coffee

    node 本身的库很基础,不足以完成日常所需,但是它的三方库往往是最容易使用和获取的。正是如此,用之前准备工作要更多

    由于没有主流系统自带 node,你需要先安装它,然后配置系统环境,这个步骤必不可少。
    执行 echo `npm config get prefix`/lib/node_modules,将获得的结果设置到环境变量 NODE_PATH 中,当然你不担心 npm 龟速的话,也可以直接把如下代码加入到你的 bashrc 之类的文件里:

    export NODE_PATH=`npm config get prefix`/lib/node_modules
    

    有了这个环境变量后我们才可以 require 全局安装的三方库。然后我们再添加个 shell 函数:

    c() {
        coffee -e '
    require "shelljs/global"
    F = require "fs"
    puts = (args...) -> console.log args
    $1'
    }
    

    然后我们就可以安装一些三方库来测试下效果了,比如执行 npm i shelljs 安装好三方库,然后执行 c 'puts ls "*"',如果正常打印了,当前目录的文件就基本配置完毕了。

    在 require 一堆三方库之后 node 的解决实际问题的能力会非常强大,V8 的爆发处理能力虽然吃内存,但很多下情况会比 ruby 或者 python 快很多,异步 IO 也会在处理慢移动磁盘时很便利。

    这里只提一个常见的问题,关于 coffee 的单行代码怎么写的问题。这个问题上 coffe 和 python 最大的不同在于多了一个 then 关键字:

    • c 'for i in [1..10] then puts i'
    • ctry F.readFileSync 'a.txt'; catch err then puts err'`

    当然,由于 js 是最 buggy 的 duck typing 语言之一, 外加大部分的功能和库都是能用 chain 范式完成功能的,one line code 非常容易写。

    如果你是个三方库控的前端人员,且不畏惧 js 的种种 buggy 问题,node 会是不二选择。

    Perl & PHP

    当前 2015 年初,perl + php 这方面的处理库实际上比 node 要多很多。而且 perl 或者 php 解释器大部分系统自带,生态环境非常无解的强大。
    除了语法相对于后来的语言来说有些不简练,找不到什么不选它的理由。perl 我也不多写例子了,官方教程 hello world 之后都不是教你 for 循环之类的,直接就上文件 IO 操作,由此可见一斑。

    如果如果习惯了用 shell,并且觉得在沙漠中寻找绿洲才是王道,perl 在等你。

    Python

    这些年的使用经验告诉我,它比较适合对 python 知根知底的人,初学者想用它玩弄文件系统会碰到各种问题,首先把多个命令写到一行就会有很多问题。再比如 python2 和 python3 的一些问题,文件名编码的问题等。

    由于本人学识尚浅,这里就不在各位看官面前班门弄斧举 python 的例子了。

    如果你是个爬行动物爱好者,并且觉得你之前学的 python 技能能无坚不摧,请选择 python。

    Others

    Go,Haskell 之类的静态类型编译语言都不在讨论范围内。

    总结

    当然上述方法对我来说可能有些过时了,现在处理一些难以复用的任务都是用 sublime 或 vim 可视化(非编程)完成。由于具体步骤太直观可视化,这里难以用文字描述,就不赘述了,有机会的话可以录视屏演示下。

    常复用的也不会用命令行敲了,太浪费生命,直接写成库或者 snippet 存在 gist 之类的地方。此外需要高性能的时候也不会不停的在 shell 里 loop 调用类似 mv, cp 之类的程序,而是直接写 C 之类的调用内核方法。

    写这么多不是想说你应该学会 ruby 什么的,每个工具都有它适用的场景:

    我们往往最需要的可能是锻炼想象力,而不是评判什么工具最好,否则我们的记忆力永远不够用。

    原文地址

    第 1 条附言  ·  2015-01-12 01:08:37 +08:00
    我是常常用 grep sed 之类的,我写这篇并不是表述大家不要使用他们,我只是在回应 http://v2ex.com/t/160899 这篇的疑问。我们确实可能不用 grep sed 而使用我们熟悉的技术快速解决问题。比如我会 ruby 我能办到,你可能会其他语言也能办到,比如 perl 或者 php。

    如果吐槽不会 ruby 如何看示例,可以读读第 25 条回复。
    32 条回复    2015-06-11 14:20:22 +08:00
    Havee
        1
    Havee  
       2015-01-11 12:03:49 +08:00
    这里记得是禁止原文转载的吧
    ysmood
        2
    ysmood  
    OP
       2015-01-11 12:05:04 +08:00
    @ysmood 请注意看作者,都是我
    jason52
        3
    jason52  
       2015-01-11 12:06:37 +08:00
    我觉得这既不是 发现了新领域,也不是解决了新问题。 有点类似于 如何在不使用轮子的情况下让汽车可以行驶的问题。

    虽然在一定的限制条件下,可以提出一种解决问题的方案,但是价值几许依旧值得考虑。你可以说我铺一个磁悬浮的轨道,让汽车可以 **实现** 在不使用轮子的情况下到达另一个目的地,但是灵活性,经济角度等权衡考虑,此解决方法未必就比使用轮子更好。

    当然还可以从启发人的角度进行进一步探讨。这就是工程之外的问题了。

    楼主也并未否认命令行工具的价值,并且给出了每个工具都有它适用的场景的结论,但是从锻炼想象力的角度进行论述,在这个场景下有点不太合理。
    ysmood
        4
    ysmood  
    OP
       2015-01-11 12:07:48 +08:00
    @Havee 最近 Github 比较难打开,就全部贴过来了,同步发布的,不知道算不算违规。
    ysmood
        5
    ysmood  
    OP
       2015-01-11 12:10:40 +08:00
    @jason52 思维可能有点跳跃了,大概想表达,利用好自己熟悉的东西,用创造力让它发挥更好的作用,往往最能解决当下所需。比如利用 pipe + python 解决 python 难以单行写代码的问题。
    xcv58
        6
    xcv58  
       2015-01-11 12:20:01 +08:00
    grep, sed, awk 连 pipe 都不需要用。
    另外它们或者替代品都有专门的优化。
    不明白为啥要脱离它们。
    aheadlead
        7
    aheadlead  
       2015-01-11 12:21:22 +08:00
    新技能学习..
    ysmood
        8
    ysmood  
    OP
       2015-01-11 12:29:07 +08:00
    @xcv58 是看了这篇的评论有感而发 http://v2ex.com/t/160899。我平时还是会大量使用 grep sed 的。只是提供一些可能的思考方式,抛砖引玉用。
    ysmood
        9
    ysmood  
    OP
       2015-01-11 12:33:35 +08:00
    @xcv58 另外如果能不用 pipe 而写的比我上面的例子更简短易懂,求赐教,我也是想扩展下思路,先谢谢了~
    xcv58
        10
    xcv58  
       2015-01-11 12:34:58 +08:00
    @ysmood 个人观点,这些都是工具,哪个适合当时的场景就用哪个。
    很多时候已经有了 best practice 的时候真的没必要费时间用其他工具再来做一遍。
    因为理论上都是基于图灵机,但是实践上就天差地别了。
    acoada
        11
    acoada  
       2015-01-11 12:36:54 +08:00
    grep太好用了,sed和awk写些简短的逻辑也好用,复杂的就perl处理,它们之间有冲突吗?
    曾经尝试过awk script,实在是。。
    ysmood
        12
    ysmood  
    OP
       2015-01-11 12:45:29 +08:00
    @xcv58 我也是昨天刚看了电影《The Imitation Game》,我这儿也不是想跟任何最佳实践较真。我文中最后也表达了类似观点,只是我觉得我们可以在使用 best practice 的时候不要忘记自己的思考,以及思考的乐趣。我写这文纯是想娱乐下大家,若是觉得有趣笑一笑,我就非常感激了,即使觉得我愚钝,我也没什么想反驳的,毕竟我也是刚入门~
    xcv58
        13
    xcv58  
       2015-01-11 12:58:52 +08:00
    @xcv58
    第一个:
    ag -m 1 -g '.*[0-9].*'

    第二个:
    ls -1 | sed s/y/"$(seq -f "0" -s '' 30)"/
    把你的 Alias 转义过来应该比这个长吧。

    第三个应该用 awk 和 xargs 来做就行了啊,懒得写了。
    xcv58
        14
    xcv58  
       2015-01-11 13:03:02 +08:00
    @ysmood 我不是反对思考,我认为不应该放到这种一次性的工作上。
    xcv58
        15
    xcv58  
       2015-01-11 13:08:10 +08:00
    @ysmood 当然你如果觉得这样很锻炼想象力的话,我也没啥好反对的。
    ysmood
        16
    ysmood  
    OP
       2015-01-11 13:49:01 +08:00
    @xcv58 ag 是啥?你也可以用 alias 啊,这个不反应逻辑的长短性啊,没必要在意这个。入口点命令长度忽略,只看作用部分。第二个你这么写明显复杂很多吧?ag 应该就是某种 grep 的 alias 吧?

    我第一个作用部分只有 `ls`[/.*\d.*/],第二个只有 $_.gsub! /y/, "o" * 30
    9hills
        17
    9hills  
       2015-01-11 13:49:28 +08:00
    Python one-line 坑比较多,而且容易出问题。不过谨慎点还是可以搞的

    比如我最常用的是用Python在命令行解析JSON格式,感觉略烦。。。但是还好吧

    curl http://xxxxx/api/v1/xxx.json | python -c "import sys,json; r=json.loads(sys.stdin.read()); print '\n'.join([i for i in r['data'] if i.startswith('aaa')]) + '\n'"

    awk/sed 处理JSON 显然是力不从心,jq这样的外部工具也不太适合(毕竟不是标准工具)
    9hills
        18
    9hills  
       2015-01-11 13:52:59 +08:00
    Python用户可以试试 "https://github.com/Russell91/pythonpy"

    py 'expression' ≅ python -c 'print(expression)'
    ysmood
        19
    ysmood  
    OP
       2015-01-11 14:10:15 +08:00
    @9hills 我平时都是写个 shell 函数来做,比如 ping 一个域名用下面这个函数就可以直接 ping 任意一个网址,而不是只能 ping 标准 host:

    ys-ping http://test.com/other/path?with=query

    ys-ping test.com

    ys-ping () {
    ret=$(python -c "
    import urlparse
    s = '$1'
    if s.find('://') < 0:
    s = 'http://' + s
    host = urlparse.urlparse(s).netloc
    print(host)
    ")
    ping $ret
    }

    一行写 python 太伤神,还是换个方式来比较好。

    话说我很好奇,怎么没有 ruby 党站出来吐个槽什么的。
    xcv58
        20
    xcv58  
       2015-01-11 14:23:59 +08:00 via iPhone
    @ysmood http://geoff.greer.fm/ag/ 搜索一下 不好乱猜
    lsmgeb89
        21
    lsmgeb89  
       2015-01-11 14:29:48 +08:00
    awk,sed,grep 要比 python 和 ruby 略快吧?
    ysmood
        22
    ysmood  
    OP
       2015-01-11 14:41:49 +08:00
    @lsmgeb89 通常情况下 C 处理速度肯定更快,我文章后面也提到了这个问题。不过你感兴趣的话可以看看这篇 “纯 js 写的 mysql parser,比 C 快” http://2012.jsconf.eu/speaker/2012/09/05/faster-than-c-parsing-node-js-streams-.html

    我的理解是某种特定情况下脚本语言可能在字符串处理上能更容易优化解释器。而静态编译语言要编写同等优化程度的解释器,虽然是可能办到的,会难太多,以至于人们更愿意节省掉这部分时间去创造新的方案。仅仅个人观点,可能是错误的。
    ysmood
        23
    ysmood  
    OP
       2015-01-11 14:49:47 +08:00
    @xcv58 ag 还是上 github 页吧 https://github.com/ggreer/the_silver_searcher。看来可以用于替换 grep 了,自动 .gitignore 这个非常不错,长姿势了!
    RemRain
        24
    RemRain  
       2015-01-11 15:07:15 +08:00
    其实我觉得直接用 grep awk sed 处理挺好,为啥要脱离呢,楼主给的几个例子,优点都是可以少打几个字符,但少打的这几个字符并没有省下多少力气,反而更费事。平时在打命令的时候,我特别怕各种标点符合,尤其下以下几种:

    1. 引号。由于引号都是成对出现的,一旦使用了,就得用一对,另外必须思考用单引号还是双引号,是否有嵌套的情况,是否有字符需要转义。

    2. 反撇号。输出可能有空格、换行之类的,使用了反撇号的情况下,有可能还得再用引号。

    3. $、! 及转义符号。

    4. 各种括号。

    一旦有上述的标点符号各种嵌套,输入不顺手不说,可能得调试几次,命令才能正确执行。相比多一两个参数,命令长一点反而是好事。

    说下楼主的两个例子:
    1. 打印第一个含有数字的文件名
    ruby: r 'puts `ls`[/.*\d.*/]'
    引号、反撇号嵌套,还有转义和通配符,输入起来很别扭,自己理解也略吃力

    bash: ls -1 | grep -m 1 -e '.*[0-9].*'
    虽然命令长了一些,但少了反撇号和转义,输入更顺手,理解也更容易一些。当然,我更喜欢写成这样:
    ls |grep '[0-9]' |head -n1

    2. 替换所有文件名中的 y 为 30 个 o
    bash: ls -1 | sed s/y/oooooooooooooooooooooooooooooo/
    ruby: ls -1 | rp '$_.gsub! /y/, "o" * 30'

    我觉得打命令的时候,bash 版的写法明显更顺手,重复内容多打几次并不难。


    实际操作过程中,sh 无处不在,而其他语言确因环境而异,各种不统一。就我这周的情况来说,操作过如下环境:自己和别人的 Terminal(Mac)、公司生产环境(rh 及 CentOS 都有)、自己的 vps(Arch)、测试机、朋友的阿里云服务器(Ubuntu)。用的都是各种 sh,不依赖 alias 和环境配置,毫无压力。
    ysmood
        25
    ysmood  
    OP
       2015-01-11 16:04:47 +08:00
    @RemRain 是的,只要入口点是 shell 就无法避免引号的问题,这个就算用 grep sed 之类的也是很头疼的问题,比如我第三个例子里有文件的文件名本身含有引号和空格的时候,grep sed 需要再额外处理一次转义,这个时候反而能体现出一般脚本语言的优势。

    我举 ruby 的例子大概是希望知道 ruby 基础知识的人能不费力的看懂单引号内的代码,外围的 r '' 是固有代码,所以不会是视觉中心。这里面的代码是不需要任何多余转义的,跟正常 ruby 一样。

    比如第一个里面的 puts `ls`[/.*\d.*/] 这一段体现了很多 ruby 有意思的特性,比如类似 subshell 的撇号 `ls` 能返回系统命令的 stdout 到一个 string 变量,然后这个 string 变量可以在数组运算符里写 正则 [/.*\d.*/] 来选择想要的部分,这设计得非常合乎常理,任何一个人都应该能感到这种统一的简洁性。

    第二个 $_.gsub! /y/, "o" * 30

    这个例子也更能体现有意思的地方,比如 $_ 就是我们熟悉的 shell 变量,ruby 里也有,gsub 用于替换也是很常见的命名方式,一个字符串乘以数字代表字符串重复 n 次:“o” * 30 (python 支持这种写法)。这些表现都比 shell 的写法更合乎人的一般思维,不是吗?

    第三个要真的写 shell,转义文件名里的特殊字符都需要写费心写一段转义处理。这边一个

    F.mv a, b

    就解决了这个问题。由于是内核的 mv 方法,传两个字符串进去,即使字符里有再多的空格,单双引号,反斜线,也完全不用像 shell 脚本那样绕来绕去。

    配置环境我都是跑一个脚本自动 deploy 到各个机器的,不费神的,也不是什么特别不能夸平台的语言或配置。甚至在 Windows 里,没有 grep,sed 等工具的情况下他们也能正常使用,毕竟 WIndows 里装一个 ruby 比装一套 cygwin 之类工具可能更省时又少 bug。
    rail4you
        26
    rail4you  
       2015-01-11 16:38:52 +08:00
    楼主的方案必须熟悉ruby才行,其实ruby和python都不太适合写one line,这是awk和sed的强项。

    alias rp="r_init -p -e"
    ls -1 | rp '$_.gsub! /y/, "o" * 30'

    这个例子很明显,不懂ruby的用户很难理解代码的意义。

    ls -1 | sed s/y/oooooooooooooooooooooooooooooo/

    sed就算写30个o,我也能看明白。

    awk和sed相当成熟,很多linux的教程都有关于awk和sed的示例,熟悉一下规则,就能写出简单实用的命令组合。
    ruby和python适合写完整脚本,个人感觉python的语法和标准库更好用一些。
    caizixian
        27
    caizixian  
       2015-01-11 19:34:03 +08:00
    @Livid 全文转载
    ysmood
        28
    ysmood  
    OP
       2015-01-12 01:13:43 +08:00
    @rail4you 我在文章后面加了一条附言,可能误解了我的文意。

    ruby 和 python 不同,是适合写 one line 的,它有 do 和 end 关键字,不受缩进等格式限制。而且函数式编程大部分问题是 chain each map reduce 来解决的,常规问题 one line + 强大的原生库都能解决。

    python 党的话可以看看第 18 条回复,是非常不错的 one line 选择。
    ysmood
        29
    ysmood  
    OP
       2015-01-12 01:15:46 +08:00
    @caizixian 这是是原创,请看第 4 条回复。
    rail4you
        30
    rail4you  
       2015-01-12 11:58:28 +08:00
    ruby不适合写one line,python也一样。我后面说的是两者写完整脚本都不错。

    one line要求编程语言精炼,awk和sed在此方面无可比拟,perl也是一样,设计思路都是简洁大于一切。ruby和python能完成one line工作,但实用性上差太多。

    文章标题,脱离这个词语气太强了,shell里很难脱离awk,sed等基础工具。后面的回复基本都在争论语言特性,你也浪费精力维护你的观点。

    你换个说法就好多了,例如ruby或者python如何完成awk或者sed的工作?。
    ysmood
        31
    ysmood  
    OP
       2015-01-12 18:17:23 +08:00
    @rail4you 嗯,有道理,标题确实应该换一下。可惜 v2ex 不能像 stackoverflow 或 quora 那样随时更换标题,发布五分钟之后就 lock 了。
    twinsant
        32
    twinsant  
       2015-06-11 14:20:22 +08:00
    作为泡CPUG的爱好者来提示:如果用Python的话,可以用iPython
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1790 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 16:32 · PVG 00:32 · LAX 08:32 · JFK 11:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.