使用Unix工具处理文本:搜索并替换不在某些行之间的所有文本

时间:2011-10-11 04:19:45

标签: python perl bash sed awk

我希望对一堆* .org文件进行一些文本处理。我想在每个文件中更改以下内容:

[my description](link)

[[link][my description]]

`some text`

=some text=

## some heading

** some heading

*some italics*

/some italics/

**some bold**

*some bold*

。是的,这是org-mode语法的IS markdown语法。我知道pandoc。需要注意的是,我想要进行上述更改,除非它们出现在以下块中:

#+BEGIN_EXAMPLE
don't want above changes to take place in this block
...
#+END_EXAMPLE

因此,我不能使用pandoc。我想根据上述要求使用某种unix脚本来处理这些文件:awk,sed,python,perl,bash等。一旦我有了一个工作脚本,我就可以修改它并从中学习。

感谢您的帮助!

3 个答案:

答案 0 :(得分:6)

Perl解决方案

这是我为@ jkerian脚本建议的简化更改的结果:使用触发器操作符和-p。我还修复了他的正则表达式,在RHS中使用了正确的$1$2,修改了从s///s:::的分隔符以避免LTS (“Leaning Toothpick综合症“),并添加/x以提高可读性。在处理粗体和斜体时存在逻辑错误,我已经修复了。我添加了注释,显示每种情况下的转换应该与原始问题描述相对应,并对齐转换的RHS以使它们更易于阅读。

#!/usr/bin/perl -p
#
# the -p option makes this a pass-through filter
#####################################################

# omit protected region
next if /^#\+BEGIN_EXAMPLE/ .. /^#\+END_EXAMPLE/;

# `some text`                      ⇒   =some text=
s: ` ( [^`]* ) `                       :=$1=:gx;

# [desc](link)                     ⇒   [[link][desc]]
s: \[ ( [^]]* ) \] \( ( [^)]* ) \)     :[[$2][$1]]:gx;

# ^## some heading ⇒ ** some heading
#      NB: can't use /x here or would have to use ugly \#
s:^##:**:;   

# *some italics*                   ⇒   /some italics/
s: (?!< \* ) \* ( [^*]+ ) \* (?! \*)   :/$1/:gx;

# **some bold**                    ⇒   *some bold*
s: \*{2} ( [^*]+ ) \*{2}               :*$1*:gx;

看看这有多容易? Perl中只有6行简单易读的代码。在Perl中很容易,因为Perl专门用于编写这种过滤器非常简单,而Python则不然。 Python有不同的设计目标。

虽然你当然可以在Python中重写它,但是不值得这么麻烦,因为Python根本就不是为这类东西而设计的。 Python缺少隐式循环和隐式打印的-p“make-me-a-filter”标志。 Python缺少隐式累加器变量。 Python缺少内置的正则表达式。 Python缺少s///运算符。 Python缺少有状态的翻转操作符。所有这些都有助于使Perl解决方案比Python解决方案更容易阅读,编写和维护。

但是,你不应该认为这总是存在的。它没有。在其他领域,您可以提出Python在这些方面领先的问题。但不是在这里。这是因为这个过滤器是Perl专注的专业领域,而不是Python。

Python解决方案因此比这个简单的Perl版本更长,更嘈杂,更难以阅读 - 因此更难维护 - 因为Perl旨在简化操作,这是其目标应用之一区域。尝试用Python重写它,并注意它是多么令人讨厌。当然有可能,但不值得麻烦,或维护噩梦。

Python版本

#!/usr/bin/env python3.2

from __future__ import print_function

import sys
import re

if (sys.version_info[0] == 2):
    sys.stderr.write("%s: legacy Python detected! Please upgrade to v3+\n"
                   % sys.argv[0] )
    ##sys.exit(2)

if len(sys.argv) == 1:
    sys.argv.append("/dev/stdin")

flip_rx = re.compile(r'^#\+BEGIN_EXAMPLE')
flop_rx = re.compile(r'^#\+END_EXAMPLE')

#EG# `some text`  -->   =some text=
lhs_backticks = re.compile(r'` ( [^`]* ) `', re.VERBOSE)
rhs_backticks =            r'=\1='

#EG# [desc](link)  -->  [[link][desc]]
lhs_desclink  = re.compile(r' \[ ( [^]]* ) \] \( ( [^)]* ) \) ', re.VERBOSE)
rhs_desclink  =            r'[[\2][\1]]'

#EG# ^## some heading  -->  ** some heading
lhs_header    = re.compile(r'^##')
rhs_header    =            r'**'

#EG# *some italics*  -->  /some italics/
lhs_italics   = re.compile(r' (?!< \* ) \* ( [^*]+ ) \* (?! \*)  ', re.VERBOSE)
rhs_italics   =            r'/\1/'

## **some bold**  -->  *some bold*
lhs_bold      = re.compile(r'\*{2} ( [^*]+ ) \*{2}', re.VERBOSE)
rhs_bold      =            r'*\1*'

errcnt = 0

flipflop = "flip"

for filename in sys.argv[1:]:
    try:
        filehandle = open(filename, "r")
    except IOError as oops:
        errcnt = errcnt + 1
        sys.stderr.write("%s: can't open '%s' for reading: %s\n"
                      % ( sys.argv[0],    filename,        oops) )
    else:
        try:
            for line in filehandle:

                new_flipflop = None

                if flipflop == "flip":
                    if flip_rx.search(line):
                        new_flipflop = "flop"
                elif flipflop == "flop":
                    if flop_rx.search(line):
                        new_flipflop = "flip"
                else:
                    raise FlipFlop_SNAFU

                if flipflop != "flop":
                    line = lhs_backticks . sub ( rhs_backticks, line)
                    line = lhs_desclink  . sub ( rhs_desclink,  line)
                    line = lhs_header    . sub ( rhs_header,    line)
                    line = lhs_italics   . sub ( rhs_italics,   line)
                    line = lhs_bold      . sub ( rhs_bold,      line)                        
                print(line, end="")

                if new_flipflop != None:
                    flipflop = new_flipflop

        except IOError as oops:
            errcnt = errcnt + 1
            sys.stderr.write("%s: can't read '%s': %s\n"
              % ( sys.argv[0],    filename,        oops) )
        finally:
            try:
                filehandle.close()
            except IOError as oops:
                errcnt = errcnt + 1
                sys.stderr.write("%s: can't close '%s': %s\n"
                  % ( sys.argv[0],    filename,        oops) )

if errcnt == 0:
    sys.exit(0)
else:
    sys.exit(1)

摘要

使用合适的工具来完成正确的工作非常重要。对于此任务,该工具是Perl,它只占用了7行。只有7件事要做,但不要试着告诉Python。这就像回到具有太多中断堆栈的汇编语言一样。对于这种工作来说,72行的Python显然没有被删除,所有令人痛苦的复杂性和噪音不可读的代码都显示了你究竟为什么。无论语言如何,每行代码的错误率是相同的,所以如果您在编写N行代码或10 * N行代码之间做出选择,则别无选择。

答案 1 :(得分:4)

我认为您正在寻找类似以下perl脚本的内容

while(<>) {
    if /#\+BEGIN_EXAMPLE/ .. /#\+END_EXAMPLE/ {
        print;
        next;
    }
    s/`([^`]*)`/=\1=/g;
    s/\[([^]]*)\]\(([^)]*)\)/[[\2][\1]]/g;
    s/^##/**/;
    s/\*([^\*]+)\*/\/\1\//g;
    s/\*\/([^\/]+)\/\*/*\1*/g;
    print;
}

使用cat testfile | perl scriptname.pl

运行它

对于非傻版本的python。注意:Perl是适合这项工作的工具,但是tchrist的python版本是一个糟糕的笑话,必须修复它。

from __future__ import print_function
import fileinput
import re
import sys

sys.tracebacklimit=0    #For those desperate to hide tracebacks in one-off scripts
example = 0
for line in fileinput.input():
    if example==0 and re.match(r'^#\+BEGIN_EXAMPLE',line):
        example+=1
    elif example>=1:
        if re.match(r'^#\+END_EXAMPLE',line): example-=1
    else:
        line = re. sub (r'` ( [^`]* ) `',                      r'=\1=',       line, 0, re.VERBOSE)
        line = re. sub (r'\[ ( [^]]* ) \] \( ( [^)]* ) \) ',   r'[[\2][\1]]', line, 0, re.VERBOSE)
        line = re. sub (r'^\#\#',                              r'**',         line, 0, re.VERBOSE)
        line = re. sub (r'(?!< \* ) \* ( [^*]+ ) \* (?! \*)',  r'/\1/',       line, 0, re.VERBOSE)
        line = re. sub (r'\*{2} ( [^*]+ ) \*{2}',              r'*\1*',       line, 0, re.VERBOSE)
    print(line, end="")

答案 2 :(得分:1)

只是为了笑容,这是我的python解决方案版本:

from __future__ import print_function
import fileinput, functools, re, sys

# For those desperate to hide tracebacks in one-off scripts
sys.tracebacklimit = 0
# Precompile all our patterns for speed
begin_example = re.compile(r'^#\+BEGIN_EXAMPLE').match
end_example = re.compile(r'^#\+END_EXAMPLE').match
# Use partial to eliminate lookups inside our loop
fixes = [ functools.partial(re.compile(x[0], x[2]).sub, x[1]) for x in
          (r'` ( [^`]* ) `',                      r'=\1=',       re.VERBOSE),
          (r'\[ ( [^]]* ) \] \( ( [^)]* ) \) ',   r'[[\2][\1]]', re.VERBOSE),
          (r'^\#\#',                              r'**',         re.VERBOSE),
          (r'(?!< \* ) \* ( [^*]+ ) \* (?! \*)',  r'/\1/',       re.VERBOSE),
          (r'\*{2} ( [^*]+ ) \*{2}',              r'*\1*',       re.VERBOSE),
          ]

inside = False
for line in fileinput.input():
    if inside:
        if end_example(line):
            inside = False
    else:
        if begin_example(line):
            inside = True
        for fixup in fixes:
            line = fixup(line)
    print(line, end='')