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

2015-01-11 11:38:38 +08:00
 ysmood

如何脱离 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 关键字:

当然,由于 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 什么的,每个工具都有它适用的场景:

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

原文地址

5722 次点击
所在节点    程序员
32 条回复
lsmgeb89
2015-01-11 14:29:48 +08:00
awk,sed,grep 要比 python 和 ruby 略快吧?
ysmood
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
2015-01-11 14:49:47 +08:00
@xcv58 ag 还是上 github 页吧 https://github.com/ggreer/the_silver_searcher。看来可以用于替换 grep 了,自动 .gitignore 这个非常不错,长姿势了!
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
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
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
2015-01-11 19:34:03 +08:00
@Livid 全文转载
ysmood
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
2015-01-12 01:15:46 +08:00
@caizixian 这是是原创,请看第 4 条回复。
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
2015-01-12 18:17:23 +08:00
@rail4you 嗯,有道理,标题确实应该换一下。可惜 v2ex 不能像 stackoverflow 或 quora 那样随时更换标题,发布五分钟之后就 lock 了。
twinsant
2015-06-11 14:20:22 +08:00
作为泡CPUG的爱好者来提示:如果用Python的话,可以用iPython

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/161060

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX