Использование инструментов Unix для обработки текста: поиск и замена всего текста, который не находится между некоторыми строками

Я ищу, чтобы сделать некоторую обработку текста в связке * .org файлов. Я хотел бы изменить следующее в каждом файле:

[my description](link) 

в

 [[link][my description]] 

,

 `some text` 

в

 =some text= 

,

 ## some heading 

в

 ** some heading 

,

 *some italics* 

в

 /some italics/ 

, а также

 **some bold** 

в

 *some bold* 

, Да, это синтаксис синтаксиса IS для синтаксиса org-mode. Я ЕСМЬ знаю о pandoc . Предостережение заключается в том, что я хочу, чтобы вышеупомянутые изменения, за исключением случаев, когда они встречаются в следующем блоке:

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

Следовательно, я не могу использовать pandoc. Я хотел бы обработать эти файлы в соответствии с вышеуказанными требованиями, используя какой-то unix-скрипт: awk, sed, python, perl, bash и т. Д. После того, как у меня есть рабочий скрипт, я могу его изменить и узнать.

Спасибо за вашу помощь!

Решение Perl

Это результат упрощения изменений, которые я предложил для скрипта @ jkerian: используйте оператор flipflop и -p . Я также исправлял его регулярные выражения, чтобы использовать правильные $1 и $2 в RHS, измененные разделители от s/// до s::: чтобы избежать LTS («Синтаксический синдром откидывания зубов») , и добавил /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; 

Посмотрите, как это легко? Всего лишь 6 простых строк чрезвычайно читаемого кода в Perl. Это просто в 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. Это похоже на возвращение к ассемблеру со слишком большим количеством стеков прерываний. Python на 72 строках явно не вырезается для такого рода работ, и все мучительные сложности и noisey нечитаемый код показывают, что именно почему. Частота ошибок в строке кода одинакова независимо от языка, поэтому, если у вас есть выбор между написанием N строк кода или 10 * N строк кода, выбора нет.

Я думаю, что вы ищете что-то вроде следующего скрипта 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 cat testfile | perl scriptname.pl

Для не-глупой версии python. Примечание. Perl – это правильный инструмент для работы, но версия python для tchrist – такая плохая шутка, что она должна быть исправлена.

 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="") 

Только для усмешек, вот моя версия решения 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='')