У меня есть огромный текстовый файл (~ 1,5 ГБ) с множеством строк, заканчивающихся на «.Ends».
Мне нужен linux oneliner (perl\awk\sed), чтобы найти последнее место ".Ends" в файле и добавить пару строк перед ним.
Я дважды пытался использовать tac
и наткнулся на свой perl:
Когда я использую:tac ../../test | perl -pi -e 'BEGIN {$flag = 1} if ($flag==1 && /.Ends/) {$flag = 0 ; print "someline\n"}' | tac
Сначала он печатает "someline\n" и только потом печатает .Ends
Результат:
…
.Заканчивается
кое-что
Когда я использую:tac ../../test | perl -e 'BEGIN {$flag = 1} print ; if ($flag==1 && /.Ends/) {$flag = 0 ; print "someline\n"}' | tac
Ничего не печатает.
И когда я использую:tac ../../test | perl -p -e 'BEGIN {$flag = 1} print $_ ; if ($flag==1 && /.Ends/) {$flag = 0 ; print "someline\n"}' | tac
Он печатает все дважды:
…
.Заканчивается
кто-то
.Заканчивается
Есть ли простой способ выполнить это редактирование?
Не обязательно быть с моим направлением решения, я не привередлив...
Бонус - если строки могут исходить из другого файла, было бы здорово (но на самом деле не обязательно)
Изменить
тестовый входной файл:
gla2
fla3
dla4
rfa5
.Ends
shu
sha
she
.Ends
res
pes
ges
.Ends
--->
...
pes
ges
someline
.Ends
# * some irrelevant junk * #
последняя строка файла всегда будет заканчиваться на .Ends
?
Нет, после последнего .Ends есть еще несколько строк, но меня это не волнует.
в то время как вы можете не заботиться о них (строки после последней .Ends
), это будет иметь значение при поиске решения, т. е. проще всегда заменять последнюю строку
Я уверен, что это проще, но это не имеет значения - все строки после последнего .Ends являются комментариями и информацией, ничего функционального, поэтому вставка должна быть в пределах границы .Ends.
Зачем вам нужна автоматизированная функция редактирования «файла» в одном месте? Похоже, все, что вам нужно сделать, это использовать текстовый редактор с функцией поиска.
По поводу it's not relevant
- да, есть. Если вы не указываете в своем вопросе, что могут быть строки после последнего .Ends
, и не включаете строки после последнего .Ends
в свой пример, то кто-то, пытающийся помочь вам, может разумно создать и протестировать решение, которое основано на том, что .Ends
является последнюю строчку и тем самым тратят их время и, в гораздо меньшей степени, ваше время.
Вы добавили немного пробела в конец последней строки .Ends
в своем вводе - это действительно может быть или это ошибка?
2 пробела, чтобы пропустить строку. теоретически они могут быть и во входных данных (никто не обещал, что это будет ^\.Ends$), но я просто хотел добавить строки, как вы просили выше. Я удалю их, если без них можно пройти без очереди.
Вы сказали, что хотите найти lines ending with ".Ends"
, а не lines ending with ".Ends" possibly followed by spaces or other characters
. Означает ли это, что строки также могут быть foobar.Ends
или foo.Ends.bar
или другими последовательностями символов с .Ends
в середине? Я не знаю, что означают 2 whitespaces, to skip line.
и if skip line can be taken without them
.
Входы:
$ cat test.dat
dla4
.Ends
she
.Ends
res
.Ends
abc
$ cat new.dat
newline 111
newline 222
Одна awk
идея, которая соответствует подходу tac | <process> | tac
OP:
$ tac test.dat | awk -v new_dat = "new.dat" '1;/\.Ends/ && !(seen++) {system("tac " new_dat)}' | tac
dla4
.Ends
she
.Ends
res
newline 111
newline 222
.Ends
abc
Еще одна awk
идея, которая заменяет двойные tac
вызовы двойным проходом входного файла:
$ awk -v new_dat = "new.dat" 'FNR==NR { if ($0 ~ /\.Ends/) lastline=FNR; next} FNR==lastline { system("cat "new_dat) }; 1' test.dat test.dat
dla4
.Ends
she
.Ends
res
newline 111
newline 222
.Ends
abc
НОТЫ:
test.dat
)красивый! Мне очень понравилось определение увиденного посередине, также вызов системы из лайнера для меня в новинку. Пост будет оставаться открытым еще некоторое время, чтобы посмотреть, может ли кто-нибудь предложить трюк для редактирования на месте, но ваш ответ работает и полностью законен! Спасибо.
вау, редактировать интересно. попробую и это.
Спасибо @markp-fuso, оба ваших ответа работают хорошо. первый метод (с обеими командами tac
) работает немного быстрее и лучше.
/.Ends/
будет соответствовать строке, содержащей FooEndsBar
, и вы не можете полагаться на то, что вывод system("tac " new_dat)
появится там, где вы хотите, внутри вывода вызывающей его команды awk (точно не знаю, почему, возможно, буферизация, но я видел вызываемый вывод команды приходит после всего вывода awk, а не в середине), вам нужно будет вызвать команду и использовать цикл while getline, а затем распечатать ее из awk, чтобы надежно обеспечить порядок вывода.
Я только что попробовал и не могу воспроизвести ту system()
проблему, о которой я упоминал, используя tac
в середине большого потока ввода/вывода, так что, возможно, это происходит в каком-то другом контексте (каналы в команде?), idk, но лично я все равно не стал бы доверяй этому.
@EdMorton У меня сработал файл размером 1,5 ГБ. в любом случае могу поискать /^.Ends/
и быть уверенным, что получу то, что мне нужно.
Вещи, работа которых не гарантируется, обычно работают, пока не перестанут работать. Вы не можете протестировать что-то, что может не работать, обнаружить, что это работает в ваших тестах, и сделать вывод, что оно будет работать всегда. Например, цикл awk, такой как for ( i in arr ) print i
, обычно печатает i
в определенном порядке, но иногда этого не происходит. Точно так же /^.Ends/
будет соответствовать тому, что вы хотите, а также строкам, которые вам не нужны, например. BEnds
, поэтому он, вероятно, будет делать то, что вы хотите, для данных, с которыми вы тестируете, но позже он даст сбой с другими данными.
В моем тесте ваше первое решение в 50 раз медленнее, чем у zdim, а ваше второе решение в 2 раза медленнее, чем ваше первое. TLP зашкаливает.
@ikegami, это меня не удивляет ... оба ответа читают весь исходный файл дважды; что касается решения zdim, опять же, меня не удивляет ... быстро добирается до строки (при условии, что ближе к концу файла); вы нашли решение ed
?
Сначала пусть grep
выполнит поиск, затем вставит строки с awk
.
$ cat insert
new content
new content
$ line=$(cat insert)
$ awk -v var = "${line}" '
NR==1{last=$1; next}
FNR==last{print var}1' <(grep -n "^\.Ends$" file | cut -f 1 -d : | tail -1) file
rfa5
.Ends
she
.Ends
ges
.Ends
ges
new content
new content
.Ends
ges
ges
$ cat file
rfa5
.Ends
she
.Ends
ges
.Ends
ges
.Ends
ges
ges
Ваш ответ основан на определенных махинациях с ОС, которые моя ОС (csh) не поддерживает, таких как круглые скобки и сохранение пробелов при выполнении set line=`cat insert`
, поэтому я не могу это проверить.
@user2141046, пожалуйста, прочитайте некоторые/все статьи, которые google.com/search?q=csh+why+not найдет.
@EdMorton, я ничего не могу контролировать - это то, что мне дано и что требуют мои инструменты. Я читал эти статьи, когда пытался сгладить что-то запятыми и получил 5 символов на каждый знак запятой...
@user2141046 user2141046, если ваш босс заставляет вас писать сценарии в csh, вы должны сопротивляться, поскольку это снижает вашу производительность и способность писать краткие, надежные, эффективные, переносимые решения, и я надеюсь, что ваш босс оценит этот отзыв. Я не знаю каких-либо инструментов, которые должны вызываться или вызываться из csh, а не из любой другой оболочки, но если они существуют, то они плохо продуманы и должны быть заменены другими переносимыми инструментами (или если сценарии оболочки вы должны добавить csh shebang в вершина).
Используя GNU sed
, -i.bak
создаст файл резервной копии с расширением .bak
, сохраняя исходный файл на месте.
$ sed -Ezi.bak 's/(.*)(\.Ends)/\1newline\nnewline\n\2/' input_file
$ cat input_file
gla2
fla3
dla4
rfa5
.Ends
shu
sha
she
.Ends
res
pes
ges
.Ends
--->
...
pes
ges
someline
newline
newline
.Ends
Я должен попробовать - это решение, вероятно, будет хорошо работать для небольшого файла, но для файла такого размера, с которым я имею дело, я подозреваю проблемы...
ага, как я и думал - он не справится с большим файлом
Входы:
$ cat test.dat
dla4
.Ends
she
.Ends
res
.Ends
abc
$ cat new.dat
newline 111
newline 222
Один ed
подход:
$ ed test.dat >/dev/null 2>&1 <<EOF
1
?.Ends
-1r new.dat
wq
EOF
Или как однострочный:
$ ed test.dat < <(printf '%s\n' 1 ?.Ends '-1r new.dat' wq) >/dev/null 2>&1
Где:
>/dev/null 2>&1
- полное подавление диагностических и информационных сообщений1
- перейти к строке №1?.Ends
- искать в файле строку .Ends
в обратном направлении (т. е. найти последнюю .Ends
в файле)-1r new.dat
- перейти на 1 строку назад/вверх (-1
) в файле и r
просмотреть содержимое new.dat
wq
- w
обряд и q
костюм (он же сохранение и выход)Это генерирует:
$ cat test.dat
dla4
.Ends
she
.Ends
res
newline 111
newline 222
.Ends
abc
ПРИМЕЧАНИЕ. В отличие от текущего кода OP, который записывает измененные данные в стандартный вывод, это решение изменяет исходный входной файл (test.dat
)
Я считаю, что ваш ответ работает (взломайте, оба ваших предыдущих ответа работают, и я все еще пытаюсь понять второй), но это не однострочник.
@user2141046 user2141046 re: not a one-liner
... «простое» решение - поместить код в оболочку функции или поместить в файл, а затем получить файл ... оба метода могут разрешить «однострочное» решение на командная строка
если честно ... я не пользователь ed
, поэтому на изучение и проверку этого ответа ушло около 15 минут, но во время этого исследования я вспомнил несколько примеров, когда многострочный ответ (как выше) был свернут в одну строку. .. что-то вроде (но не цитируйте меня): ed '1;?.Ends;-1r new.dat;wq' test.dat
чистый результат... во многих случаях многострочный можно свести к однострочному
@user2141046 user2141046 fwiw ... после нескольких минут беседы с г-ном Google я смог понять, как написать это и однострочником; ответ обновлен
Спасибо, но я буду придерживаться другого вашего ответа, с awk
. как говорится, работает - не чини :)
Предполагая, что последний экземпляр этой фразы находится далеко внизу файла, обработка файла сзади значительно повышает производительность, например, с помощью File::ReadBackwards.
Поскольку вам нужно добавить другой текст в файл перед последним маркером, нам нужно скопировать остальную часть, чтобы иметь возможность вернуть его после добавления.
use warnings;
use strict;
use feature 'say';
use Path::Tiny;
use File::ReadBackwards;
my $file = shift // die "Usage: $0 file\n";
my $bw = File::ReadBackwards->new($file);
my @rest_after_marker;
while ( my $line = $bw->readline ) {
unshift @rest_after_marker, $line;
last if $line =~ /\.Ends/;
}
# Position after which to add text and copy back the rest
my $pos = $bw->tell;
$bw->close;
open my $fh, '+<', $file or die $!;
seek $fh, $pos, 0;
truncate $fh, $pos;
print $fh $_ for path("add.txt")->slurp, @rest_after_marker;
Новый текст для добавления перед последним .Ends
предположительно находится в файле add.txt
.
Остается вопрос, сколько файлов осталось после последнего маркера .Ends
? Мы копируем все это в память, чтобы иметь возможность записать обратно. Если этого слишком много, скопируйте его во временный файл вместо памяти, затем используйте его оттуда и удалите файл.
Обратите внимание, что это редактирует входной файл на месте.
Это не однострочник. Код кажется правильным (и я действительно предпочитаю редактирование на месте), но это не то, о чем я просил...
@user2141046 user2141046 Ну да ... Я только что удалил примечание об этом, которое у меня было в тексте, так как я считаю это немного неуместным в целом. (Кроме того, люди часто упоминают об этом только для того, чтобы выяснить, что это не имеет значения — и некоторые другие требования здесь неясны.) Этот код делает именно то, что просят, и максимально эффективен, и это может иметь значение для Gig- полтора файла. Но не стесняйтесь отбрасывать, если это имеет значение «однострочный» (его, конечно, можно сократить и превратить в программу командной строки, но, на мой взгляд, это было бы неуместно). Я надеюсь, что это все еще полезно для других.
Я согласен и проголосовал за вас, несмотря ни на что. пусть это будет во благо :)
@ user2141046, Re "Это не однострочник", Конечно. Ничто не мешает вам поставить его в одну строку.
Поскольку вы хотите прочитать новые строки из файла:
$ cat new
foo
bar
etc
$ tac file | awk 'NR==FNR{str=$0 ORS str; next} {print} $0= = ".Ends"{printf "%s", str; str = ""}' new - | tac
gla2
fla3
dla4
rfa5
.Ends
shu
sha
she
.Ends
res
pes
ges
.Ends
--->
...
pes
ges
someline
foo
bar
etc
.Ends
# * some irrelevant junk * #
Вышеприведенное предполагает, что пробел после .Ends
в некоторых строках вашего опубликованного примера ввода является ошибкой. Если они действительно могут присутствовать, измените $0= = ".Ends"
на /^\.Ends[[:space:]]*$/
или даже /^[[:space:]]*\.Ends[[:space:]]*$/
, если в этих строках также могут быть начальные пробелы, или просто /\.Ends/
, если могут быть какие-либо символы до/после .Ends
.
Не могли бы вы объяснить, что делает тире после слова «новый» в этой команде awk? Не знаком с одинарным тире (и с псевдонимом -
на less
в моей среде, поэтому хочу предотвратить коллизии)
В каждом сценарии оболочки -
в контексте ввода представляет stdin
. Не используйте псевдоним для less
(я не знал, что вы МОЖЕТЕ использовать псевдонимы символов!), иначе у вас возникнут проблемы.
Ты прав. Сделанный.