Bash или Python, чтобы вернуться назад? - PullRequest
6 голосов
/ 18 июня 2009

У меня есть текстовый файл, в котором много случайных вхождений строки @STRING_A, и мне было бы интересно написать короткий скрипт, который удаляет только некоторые из них. Особенно тот, который сканирует файл и находит строку, начинающуюся с этой строки, например

@STRING_A

затем проверяет, есть ли 3 строки назад, есть еще одно вхождение строки, начинающейся с той же строки, например

@STRING_A


@STRING_A

и, если это произойдет, удалить вхождение на 3 строки назад. Я думал о Bash, но я не знаю, как "пойти назад" с ним. Так что я уверен, что это невозможно с bash. Я также подумал о Python, но затем я должен хранить всю информацию в памяти, чтобы вернуться назад, а затем, для длинных файлов, это было бы невозможно.

Что ты думаешь? Возможно ли сделать это в bash или python?

Спасибо

Ответы [ 11 ]

4 голосов
/ 19 июня 2009

Забавно, что по прошествии всех этих часов никто так и не дал решения проблемы в том виде, в каком оно было сформулировано (как @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; -).

2 голосов
/ 19 июня 2009

Вот более забавное решение, использующее два итератора со смещением из трех элементов:)

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,
2 голосов
/ 18 июня 2009

Конечно, Python также будет работать. Просто сохраните последние три строки в массиве и проверьте, совпадает ли первый элемент в массиве со значением, которое вы сейчас читаете. Затем удалите значение и распечатайте текущий массив. Затем вы переместились бы по своим элементам, чтобы освободить место для нового значения, и повторите. Конечно, когда массив заполнен, вы должны будете продолжать перемещать значения из массива и вставлять вновь считанные значения, останавливаясь, чтобы каждый раз проверять, соответствует ли первое значение в массиве значению, которое вы сейчас читаете.

1 голос
/ 18 июня 2009

Как сказал AlbertoPL, храните строки в fifo для последующего использования - не «возвращайтесь назад». Для этого я бы определенно использовал python поверх bash + sed / awk / что угодно.

Мне понадобилось несколько минут, чтобы закодировать этот фрагмент:

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,
1 голос
/ 18 июня 2009

Этот код сканирует файл и удаляет строки, начинающиеся с маркера. По умолчанию в памяти хранятся только три строки:

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
1 голос
/ 18 июня 2009

Почему это не возможно в bash? Вам не нужно хранить весь файл в памяти, только последние три строки (если я правильно понял), и писать, что подходит для стандартизации. Перенаправьте его во временный файл, убедитесь, что все работает как положено, и перезапишите исходный файл временным.

То же самое относится и к Python.

Я бы предоставил собственный сценарий, но он не будет проверен. ; -)

0 голосов
/ 19 июня 2009

Этот «ответ» предназначен для лир ... Я исправлю свой предыдущий комментарий: если стрелка находится в первых 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):] вместо .replace(needle, "")

>>> lines = "NEEDLE x NEEDLE y\nnoddle\nnuddle\n".splitlines(True)
>>> needle = "NEEDLE"
>>> # Expected result: no change to the file
... 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)
 x  y   <<<=== whoops!
noddle
nuddle
        <<<=== still got unwanted newline in here
>>>
0 голосов
/ 19 июня 2009

Это может быть то, что вы ищете?

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
0 голосов
/ 19 июня 2009

Мой awk-fu никогда не был так хорош ... но следующее может дать вам то, что вы ищете в форме утилиты bash-shell / shell:

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.

Редактировать: Алекс Мартелли отмечает, что приведенный выше пример файла мог сбить меня с толку. (мой код выше удаляет всю строку, а не только флаг @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
0 голосов
/ 18 июня 2009

Я бы подумал об использовании sed. GNU SED поддерживает определение диапазонов строк. если sed потерпит неудачу, есть еще один зверь - awk, и я уверен, что вы можете сделать это с помощью awk.

O.K. Я чувствую, что должен поставить свой awk POC. Я не мог понять, чтобы использовать адреса sed. Я не пробовал комбинацию awk + sed, но мне кажется это излишним.

Мой скрипт на awk работает следующим образом:

  • Он читает строки и сохраняет их в 3-х строчном буфере

  • как только нужный шаблон найден (в моем случае /^data.*/), ищется трехстрочный буфер, чтобы проверить, был ли выбранный шаблон три строки назад

  • если шаблон был замечен, то 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++;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...