Существует ли служебное приложение командной строки, которое может найти определенный блок строк в текстовом файле и заменить его?
ОБНОВЛЕНИЕ (см. Конец вопроса)
Текст служебных программ "искать и заменять", которые я видел, похоже, ищет только построчно...
Существует ли инструмент командной строки, который может найти один блок строк (в текстовом файле) и заменить его другим блоком строк?
Например: содержит ли файл тестового файла это exact group
из линий:
'Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,
And the mome raths outgrabe.
'Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
The frumious Bandersnatch!'
Я хочу это, чтобы я мог заменить несколько строк текста в файле и знать, что я не перезаписываю неправильные строки.
Я бы никогда не заменил "Jabberwocky" (Льюис Кэрролл), но это новый пример:)
ОБНОВЛЕНИЕ:
.. (вспомогательное обновление) Мой следующий комментарий о причинах, когда не используется sed, только в контексте; не выдвигайте какой-либо инструмент слишком далеко от его замысла (я часто использую sed, и считаю его неоценимым).
Я только сейчас нашел интересную веб-страницу о sed и о том, когда им не пользоваться.
Итак, из-за всех ответов Sed, я опубликую ссылку.. это часть Sed FAQ на sourceforge
Кроме того, я уверен, что есть какой-то способ diff
может сделать работу по поиску блока текста (как только он найден, замена довольно проста; head
а также tail
) ... 'diff' сбрасывает все необходимые данные, но я еще не понял, как их фильтровать, ... (я все еще работаю над этим)
5 ответов
Этот простой скрипт на Python должен выполнить задачу:
#!/usr/bin/env python
# Syntax: multiline-replace.py input.txt search.txt replacement.txt
import sys
inp = open(sys.argv[1]).read()
needle = open(sys.argv[2]).read()
replacement = open(sys.argv[3]).read()
sys.stdout.write(inp.replace(needle,replacement))
Как и у большинства других решений, у него есть недостаток, заключающийся в том, что весь файл помещается в память одновременно. Однако для небольших текстовых файлов это должно работать достаточно хорошо.
Подход 1: временно изменить переводы строк на что-то другое
Следующий фрагмент заменяет строки на новые, выполняет замену и заменяет разделители. Утилита может захлебнуться, если строка видит ее очень долго. Вы можете выбрать любой символ для замены, если он отсутствует в строке поиска.
<old.txt tr '\n' '|' |
sed 's/\(|\|^\)'\''Twas … toves|Did … Bandersnatch!'\''|/new line 1|new line 2|/g' |
tr '|' '\n' >new.txt
Подход 2: изменить разделитель записей утилиты
Awk и Perl поддерживают установку двух или более пустых строк в качестве разделителя записей. С awk, пас -vRS=
(пусто RS
переменная). С Perl пас -000
("Режим абзаца") или установить $,=""
, Это не полезно здесь, так как у вас есть строка поиска из нескольких абзацев.
Awk и perl также поддерживают установку любой строки в качестве разделителя записей. Установлен RS
или же $,
на любую строку, которой нет в строке поиска.
<old.txt perl -pe '
BEGIN {$, = "|"}
s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt
Подход 3: работа над всем файлом
Некоторые утилиты позволяют легко читать весь файл в память и работать с ним.
<old.txt perl -0777 -pe '
s/^'\''Twas … toves\nDid … Bandersnatch!'\''$/new line 1\nnew line 2/mg
' >new.txt
Подход 4: программа
Читайте строки по одной. Начните с пустого буфера. Если вы видите строку "Twas" и буфер пуст, поместите его в буфер. Если вы видите "Did gyre" и в буфере есть одна строка, добавьте текущую строку в буфер и так далее. Если вы только что добавили строку "Bandersnatch", выведите текст замены. Если текущая строка не попала в буфер, выведите содержимое буфера, напечатайте текущую строку и очистите буфер.
psusi показывает реализацию sed. В sed концепция буфера встроена; это называется трюмом. В awk или perl вы просто используете переменную (возможно, две, одну для содержимого буфера и одну для числа строк).
ОБНОВЛЕНИЕ: Python-скрипт loevborg, безусловно, является самым простым и лучшим решением (в этом нет никаких сомнений), и я очень доволен этим, но я хотел бы отметить, что bash-скрипт, который я представил (в конце вопроса) это далеко не так сложно, как кажется... Я обрезал весь отладочный мусор, который я использовал для его тестирования... и здесь снова без перегрузки (для тех, кто посещает эту страницу).. Это в основном sed
однострочный, с пре- и пост-шестнадцатеричным преобразованием:
F=("$haystack" "$needle" "$replacement")
for f in "${F[@]}" ; do cat "$f" | hexdump -v -e '1/1 "%02x"' > "$f.hex" ; done
sed -i "s/$(cat "${F[1])}.hex")/$(cat "${F[2])}.hex")/p" "${F[0])}.hex"
cat "${F[0])}.hex" | xxd -r -p > "${F[0])}"
# delete the temp *.hex files.
Просто чтобы бросить свою шляпу на ринг, я придумала решение "sed", которое не будет сталкиваться с какими-либо проблемами со специальными символами регулярных выражений, потому что оно не использует даже ни одного!.. вместо этого он работает на Hexdumped версиях файлов...
Я думаю, что он слишком "очень тяжелый", но он работает и, по-видимому, не ограничен какими-либо ограничениями по размеру. GNU sed имеет неограниченный размер буфера шаблонов, и именно здесь заканчивается блок Hexdumped строк поиска. в этом отношении все нормально...
Я все еще ищу diff
решение, потому что оно будет более гибким в отношении пустого пространства (и я бы ожидал; быстрее)... но до тех пор... Это знаменитый мистер Сед.:)
Этот скрипт полностью запущен как есть и имеет разумные комментарии...
Это выглядит больше, чем оно есть; У меня есть только 7 строк необходимого кода.
Для полуреалистичного теста он загружает книгу "Алиса сквозь зеркало" от Project Gutenberg (363,1 КБ)... и заменяет оригинальное стихотворение Jabberwocky своей версией с обращенной строкой. (Интересно, что это не так уж много другое чтение это назад:)
PS. Я только что понял, что недостатком этого метода является то, что ваш оригинал использует \r\n (0xODOA) в качестве новой строки, и ваш "текст для соответствия" сохраняется с \n (0x0A).. тогда этот процесс сопоставления мертв в вода... (у 'diff' нет таких проблем)...
# In a text file, replace one block of lines with another block
#
# Keeping with the 'Jabberwocky' theme,
# and using 'sed' with 'hexdump', so
# there is no possible *special* char clash.
#
# The current setup will replace only the first instance.
# Using sed's 'g' command, it cah change all instances.
#
lookinglass="$HOME/Through the Looking-Glass by Lewis Carroll"
jabberwocky="$lookinglass (jabberwocky)"
ykcowrebbaj="$lookinglass (ykcowrebbaj)"
##### This section if FOR TEST PREPARATION ONLY
fromURL="http://www.gutenberg.org/ebooks/12.txt.utf8"
wget $fromURL -O "$lookinglass"
if (($?==0))
then echo "Download OK"
else exit 1
fi
# Make a backup of the original (while testing)
cp "$lookinglass" "$lookinglass(fromURL)"
#
# Extact the poem and write it to a file. (It runs from line 322-359)
sed -n 322,359p "$lookinglass" > "$jabberwocky"
cat "$jabberwocky"; read -p "This is the original.. (press Enter to continue)"
#
# Make a file containing a replacement block of lines
tac "$jabberwocky" > "$ykcowrebbaj"
cat "$ykcowrebbaj"; read -p "This is the REPLACEMENT.. (press Enter to continue)"
##### End TEST PREPARATION
# The main process
#
# Make 'hexdump' versions of the 3 files... source, expected, replacement
cat "$lookinglass" | hexdump -v -e '1/1 "%02x"' > "$lookinglass.xdig"
cat "$jabberwocky" | hexdump -v -e '1/1 "%02x"' > "$jabberwocky.xdig"
cat "$ykcowrebbaj" | hexdump -v -e '1/1 "%02x"' > "$ykcowrebbaj.xdig"
# Now use 'sed' in a safe (no special chrs) way.
# Note, all files are now each, a single line ('\n' is now '0A')
sed -i "s/$(cat "$jabberwocky.xdig")/$(cat "$ykcowrebbaj.xdig")/p" "$lookinglass.xdig"
##### This section if FOR CHECKING THE RESULTS ONLY
# Check result 1
read -p "About to test for the presence of 'jabberwocky.xdig' within itself (Enter) "
sed -n "/$(cat "$jabberwocky.xdig")/p" "$jabberwocky.xdig"
echo -e "\n\nA dump above this line, means: 'jabberwocky' is as expected\n"
# Check result 2
read -p "About to test for the presence of 'ykcowrebbaj.xdig' within itself (Enter) "
sed -n "/$(cat "$ykcowrebbaj.xdig")/p" "$ykcowrebbaj.xdig"
echo -e "\n\nA dump above this line, means: 'ykcowrebbaj' is as expected\n"
# Check result 3
read -p "About to test for the presence of 'lookinglass.xdig' within itself (Enter) "
sed -n "/$(cat "$ykcowrebbaj.xdig")/p" "$lookinglass.xdig"
echo -e "\n\nA dump above this line, means: 'lookinglass' is as expected\n"
# Check result 4
read -p "About to test for the presence of 'lookinglass.xdig' within itself (Enter) "
sed -n "/$(cat "$jabberwocky.xdig")/p" "$lookinglass.xdig"
echo -e "\n\nNo dump above this line means: 'lookinglass' is as expected\n"
##### End of CHECKING THE RESULTS
# Now convert the hexdump to binary, and overwrite the original
cat "$lookinglass.xdig" | xxd -r -p > "$lookinglass"
# Echo the "modified" poem to the screen
sed -n 322,359p "$lookinglass"
echo -e "\n\nYou are now looking at the REPLACEMENT text (dumped directly from the source 'book'"
Я был уверен, что должен быть способ сделать это с помощью sed. После некоторого поиска в Google я столкнулся с этим:
http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/
Исходя из этого, я написал:
sed -n '1h;1!H;${;g;s/foo\nbar/jar\nhead/g;p;}' < x
Который правильно взял содержимое х:
фу бар
И выплюнуть
кувшин
Даже если вам не нравится седой sed
а также perl
, вы все еще можете найти симпатию в серой awk
, Этот ответ, кажется, то, что вы ищете. Я воспроизвожу это здесь. Скажем, у вас есть три файла и вы хотите заменить needle
с replacement
в haystack
:
awk ' BEGIN { RS="" }
FILENAME==ARGV[1] { s=$0 }
FILENAME==ARGV[2] { r=$0 }
FILENAME==ARGV[3] { sub(s,r) ; print }
' needle replacement haystack > output
Это не включает регулярные выражения и поддерживает символы новой строки. Кажется, он работает с достаточно большими файлами. Он включает в себя весь файл в памяти, поэтому он не будет работать с файлами произвольного размера. Если вы хотите, чтобы это было более элегантно, вы можете заключить весь shebang в скрипт bash или превратить его в awk
скрипт.