Bash还是Python倒退?

时间:2009-06-18 12:56:10

标签: python bash

我有一个文本文件很多随机出现的字符串@STRING_A,我有兴趣编写一个短脚本,只删除其中的一些。特别是一个扫描文件,一旦找到一个以这个字符串开头的行,如

@STRING_A

然后检查3行向后是否有另一个以相同字符串开头的行,如

@STRING_A


@STRING_A

如果发生,则向后删除3行。我在想bash,但我不知道如何“倒退”它。所以我相信bash是不可能的。我也考虑过python,但是后来我应该将所有信息存储在内存中以便向后转,然后,对于长文件来说,这是不可行的。

你怎么看?是可以在bash或python中完成吗?

由于

11 个答案:

答案 0 :(得分:4)

有趣的是,在所有这几个小时之后,没有人给出问题的解决方案,如实际措辞(正如@John Machin在评论中指出的那样) - 只删除前导标记(如果后面跟着另一个这样的标记3行),不是包含它的整行。当然,这并不难 - 这是@ truppo有趣解决方案所需的一个小模块,例如:

from itertools import izip, chain
f = "foo.txt"
for third, line in izip(chain("   ", open(f)), open(f)):
    if third.startswith("@STRING_A") and line.startswith("@STRING_A"):
        line = line[len("@STRING_A"):]
    print line,

当然,在现实生活中,人们会使用iterator.tee而不是两次读取文件,将此代码放在函数中,不要无限地重复标记常量,& c; - )。

答案 1 :(得分:2)

当然Python也可以。只需将最后三行存储在一个数组中,并检查数组中的第一个元素是否与您当前读取的值相同。然后删除该值并打印出当前数组。然后,您将移动元素以为新值腾出空间并重复。当然,当数组填满时,你必须确保继续从数组中移出值并输入新读取的值,每次停止检查以查看数组中的第一个值是否与您的值相匹配目前正在阅读。

答案 2 :(得分:2)

这是一个更有趣的解决方案,使用两个带有三个元素偏移量的迭代器:)

from itertools import izip, chain, tee
f1, f2 = tee(open("foo.txt"))
for third, line in izip(chain("   ", f1), f2):
    if not (third.startswith("@STRING_A") and line.startswith("@STRING_A")):
        print line,

答案 3 :(得分:1)

为什么不能在bash中实现?您不需要将整个文件保留在内存中,只需要保留最后三行(如果我理解正确),并编写适合标准输出的内容。将其重定向到临时文件中,检查一切是否按预期工作,并用临时文件覆盖源文件。

Python也是如此。

我会提供自己的脚本,但不会进行测试。 ; - )

答案 4 :(得分:1)

此代码将扫描文件,并删除以标记开头的行。默认情况下,它只在内存中保留三行:

from collections import deque

def delete(fp, marker, gap=3):
    """Delete lines from *fp* if they with *marker* and are followed
    by another line starting with *marker* *gap* lines after.
    """
    buf = deque()
    for line in fp:
        if len(buf) < gap:
            buf.append(line)
        else:
            old = buf.popleft()
            if not (line.startswith(marker) and old.startswith(marker)):
                yield old
            buf.append(line)
    for line in buf:
        yield line

我用它测试了它:

>>> from StringIO import StringIO
>>> fp = StringIO('''a
... b
... xxx 1
... c
... xxx 2
... d
... e
... xxx 3
... f
... g
... h
... xxx 4
... i''')
>>> print ''.join(delete(fp, 'xxx'))
a
b
xxx 1
c
d
e
xxx 3
f
g
h
xxx 4
i

答案 5 :(得分:1)

正如AlbertoPL所说,将商品线存放在fifo中供以后使用 - 不要“倒退”。为此我肯定会使用python而不是bash + sed / awk / whatever。

我花了一些时间来编写这个代码片段:

from collections import deque
line_fifo = deque()
for line in open("test"):
    line_fifo.append(line)
    if len(line_fifo) == 4:
        # "look 3 lines backward"                                               
        if line_fifo[0] == line_fifo[-1] == "@STRING_A\n":
            # get rid of that match
            line_fifo.popleft()
        else:
            # print out the top of the fifo
            print line_fifo.popleft(),
# don't forget to print out the fifo when the file ends
for line in line_fifo: print line,

答案 6 :(得分:0)

我的awk-fu从来没有这么好......但以下内容可能会以bash-shell / shell-utility形式为您提供所需内容:

sed `awk 'BEGIN{ORS=";"}
/@STRING_A/ {
  if(LAST!="" && LAST+3 >= NR) print LAST "d"
  LAST = NR
}' test_file` test_file

基本上...... awk正在为sed生成一条删除某些行的命令。我确信有一种相对简单的方法可以让awk完成所有处理,但这确实有效。

坏的部分?它确实读了两次test_file。

好的部分?它是一个bash / shell-utility实现。

编辑:Alex Martelli指出上面的示例文件可能让我很困惑。 (我上面的代码删除整行,而不是仅删除@STRING_A标志)

通过将命令调整为sed:

可以轻松解决这个问题
sed `awk 'BEGIN{ORS=";"}
/@STRING_A/ {
  if(LAST!="" && LAST+3 >= NR) print LAST "s/@STRING_A//"
  LAST = NR
}' test_file` test_file

答案 7 :(得分:0)

这个“答案”是针对lyrae ...我将修改我之前的评论:如果针头位于文件的前3行,你的脚本将导致IndexError或访问它不应该的行访问,有时会产生有趣的副作用。

导致IndexError的脚本示例:

>>> lines = "@string line 0\nblah blah\n".splitlines(True)
>>> needle = "@string "
>>> for i,line in enumerate(lines):
...     if line.startswith(needle) and lines[i-3].startswith(needle):
...         lines[i-3] = lines[i-3].replace(needle, "")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IndexError: list index out of range

此示例不仅显示地球是圆形的,而且还显示为什么“修复”“不要删除整行”问题应该使用.replace(needle, "", 1)[len(needle):]代替{ {1}}

.replace(needle, "")

答案 8 :(得分:-1)

在bash中,您可以使用sort -r filenametail -n filename向后阅读文件。

$LINES=`tail -n filename | sort -r`
# now iterate through the lines and do your checking

答案 9 :(得分:-1)

这可能是你在找什么?

lines = open('sample.txt').readlines()

needle = "@string "

for i,line in enumerate(lines):
    if line.startswith(needle) and lines[i-3].startswith(needle):
        lines[i-3] = lines[i-3].replace(needle, "")
print ''.join(lines)

此输出:

string 0 extra text
string 1 extra text
string 2 extra text
string 3 extra text
--replaced --  4 extra text
string 5 extra text
string 6 extra text
@string 7 extra text
string 8 extra text
string 9 extra text
string 10 extra text

答案 10 :(得分:-2)

我会考虑使用sed。 gnu sed支持行范围的定义。如果sed会失败,那么还有另一个野兽 - awk,我相信你可以用awk做到这一点。

O.K。我觉得我应该把我的awk POC。我无法想出使用sed地址。我没有尝试过awk + ​​sed的组合,但在我看来它太过分了。

我的awk脚本的工作原理如下:

  • 它读取行并将它们存储到3行缓冲区

  • 一旦找到所需的模式(在我的情况下为/^data.*/),查找3行缓冲区以检查是否在三行之前看到了所需的模式

  • 如果看到图案,则划伤3行

说实话,我可能也会使用python,因为awk真的很尴尬。 AWK代码如下:

function max(a, b)
{
    if (a > b)
        return a;
    else
        return b;
}

BEGIN {
    w = 0;  #write index
    r = 0;  #read index
    buf[0, 1, 2];   #buffer

}

END {
    # flush buffer
    # start at read index and print out up to w index
    for (k = r % 3; k  r - max(r - 3, 0); k--) {
        #search in 3 line history buf
        if (match(buf[k % 3], /^data.*/) != 0) {
            # found -> remove lines from history
            # by rewriting them -> adjust write index
            w -= max(r, 3);
        }
    }
    buf[w % 3] = $0;
    w++;
}

/^.*/ {
    # store line into buffer, if the history
    # is full, print out the oldest one.
    if (w > 2) {
        print buf[r % 3];
        r++;
        buf[w % 3] = $0;
    }
    else {
        buf[w] = $0;
    }
    w++;
}