Редактирование последнего экземпляра в файле

У меня есть огромный текстовый файл (~ 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 * #

Ты прав. Сделанный.

user2141046 19.11.2022 21:56

последняя строка файла всегда будет заканчиваться на .Ends?

markp-fuso 19.11.2022 21:58

Нет, после последнего .Ends есть еще несколько строк, но меня это не волнует.

user2141046 19.11.2022 21:59

в то время как вы можете не заботиться о них (строки после последней .Ends), это будет иметь значение при поиске решения, т. е. проще всегда заменять последнюю строку

markp-fuso 19.11.2022 22:06

Я уверен, что это проще, но это не имеет значения - все строки после последнего .Ends являются комментариями и информацией, ничего функционального, поэтому вставка должна быть в пределах границы .Ends.

user2141046 19.11.2022 22:14

Зачем вам нужна автоматизированная функция редактирования «файла» в одном месте? Похоже, все, что вам нужно сделать, это использовать текстовый редактор с функцией поиска.

TLP 19.11.2022 23:56

По поводу it's not relevant - да, есть. Если вы не указываете в своем вопросе, что могут быть строки после последнего .Ends, и не включаете строки после последнего .Ends в свой пример, то кто-то, пытающийся помочь вам, может разумно создать и протестировать решение, которое основано на том, что .Ends является последнюю строчку и тем самым тратят их время и, в гораздо меньшей степени, ваше время.

Ed Morton 20.11.2022 01:28

Вы добавили немного пробела в конец последней строки .Ends в своем вводе - это действительно может быть или это ошибка?

Ed Morton 20.11.2022 12:43

2 пробела, чтобы пропустить строку. теоретически они могут быть и во входных данных (никто не обещал, что это будет ^\.Ends$), но я просто хотел добавить строки, как вы просили выше. Я удалю их, если без них можно пройти без очереди.

user2141046 20.11.2022 12:56

Вы сказали, что хотите найти 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.

Ed Morton 20.11.2022 12:59
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
10
233
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Входы:

$ 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

НОТЫ:

  • оба этих решения записывают измененные данные в стандартный вывод (то же самое, что и текущий код OP)
  • ни одно из этих решений не изменяет исходный входной файл (test.dat)

красивый! Мне очень понравилось определение увиденного посередине, также вызов системы из лайнера для меня в новинку. Пост будет оставаться открытым еще некоторое время, чтобы посмотреть, может ли кто-нибудь предложить трюк для редактирования на месте, но ваш ответ работает и полностью законен! Спасибо.

user2141046 19.11.2022 22:27

вау, редактировать интересно. попробую и это.

user2141046 19.11.2022 22:29

Спасибо @markp-fuso, оба ваших ответа работают хорошо. первый метод (с обеими командами tac) работает немного быстрее и лучше.

user2141046 20.11.2022 11:12
/.Ends/ будет соответствовать строке, содержащей FooEndsBar, и вы не можете полагаться на то, что вывод system("tac " new_dat) появится там, где вы хотите, внутри вывода вызывающей его команды awk (точно не знаю, почему, возможно, буферизация, но я видел вызываемый вывод команды приходит после всего вывода awk, а не в середине), вам нужно будет вызвать команду и использовать цикл while getline, а затем распечатать ее из awk, чтобы надежно обеспечить порядок вывода.
Ed Morton 20.11.2022 12:50

Я только что попробовал и не могу воспроизвести ту system() проблему, о которой я упоминал, используя tac в середине большого потока ввода/вывода, так что, возможно, это происходит в каком-то другом контексте (каналы в команде?), idk, но лично я все равно не стал бы доверяй этому.

Ed Morton 20.11.2022 13:18

@EdMorton У меня сработал файл размером 1,5 ГБ. в любом случае могу поискать /^.Ends/ и быть уверенным, что получу то, что мне нужно.

user2141046 20.11.2022 15:30

Вещи, работа которых не гарантируется, обычно работают, пока не перестанут работать. Вы не можете протестировать что-то, что может не работать, обнаружить, что это работает в ваших тестах, и сделать вывод, что оно будет работать всегда. Например, цикл awk, такой как for ( i in arr ) print i, обычно печатает i в определенном порядке, но иногда этого не происходит. Точно так же /^.Ends/ будет соответствовать тому, что вы хотите, а также строкам, которые вам не нужны, например. BEnds, поэтому он, вероятно, будет делать то, что вы хотите, для данных, с которыми вы тестируете, но позже он даст сбой с другими данными.

Ed Morton 20.11.2022 18:11

В моем тесте ваше первое решение в 50 раз медленнее, чем у zdim, а ваше второе решение в 2 раза медленнее, чем ваше первое. TLP зашкаливает.

ikegami 22.11.2022 20:21

@ikegami, это меня не удивляет ... оба ответа читают весь исходный файл дважды; что касается решения zdim, опять же, меня не удивляет ... быстро добирается до строки (при условии, что ближе к концу файла); вы нашли решение ed?

markp-fuso 22.11.2022 21:18

Сначала пусть 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 20.11.2022 10:17

@user2141046, пожалуйста, прочитайте некоторые/все статьи, которые google.com/search?q=csh+why+not найдет.

Ed Morton 20.11.2022 11:59

@EdMorton, я ничего не могу контролировать - это то, что мне дано и что требуют мои инструменты. Я читал эти статьи, когда пытался сгладить что-то запятыми и получил 5 символов на каждый знак запятой...

user2141046 20.11.2022 12:26

@user2141046 user2141046, если ваш босс заставляет вас писать сценарии в csh, вы должны сопротивляться, поскольку это снижает вашу производительность и способность писать краткие, надежные, эффективные, переносимые решения, и я надеюсь, что ваш босс оценит этот отзыв. Я не знаю каких-либо инструментов, которые должны вызываться или вызываться из csh, а не из любой другой оболочки, но если они существуют, то они плохо продуманы и должны быть заменены другими переносимыми инструментами (или если сценарии оболочки вы должны добавить csh shebang в вершина).

Ed Morton 20.11.2022 12:29

Используя 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

Я должен попробовать - это решение, вероятно, будет хорошо работать для небольшого файла, но для файла такого размера, с которым я имею дело, я подозреваю проблемы...

user2141046 20.11.2022 08:45

ага, как я и думал - он не справится с большим файлом

user2141046 20.11.2022 10:24

Входы:

$ 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 20.11.2022 08:49

@user2141046 user2141046 re: not a one-liner ... «простое» решение - поместить код в оболочку функции или поместить в файл, а затем получить файл ... оба метода могут разрешить «однострочное» решение на командная строка

markp-fuso 20.11.2022 14:42

если честно ... я не пользователь ed, поэтому на изучение и проверку этого ответа ушло около 15 минут, но во время этого исследования я вспомнил несколько примеров, когда многострочный ответ (как выше) был свернут в одну строку. .. что-то вроде (но не цитируйте меня): ed '1;?.Ends;-1r new.dat;wq' test.dat

markp-fuso 20.11.2022 14:45

чистый результат... во многих случаях многострочный можно свести к однострочному

markp-fuso 20.11.2022 14:45

@user2141046 user2141046 fwiw ... после нескольких минут беседы с г-ном Google я смог понять, как написать это и однострочником; ответ обновлен

markp-fuso 21.11.2022 15:39

Спасибо, но я буду придерживаться другого вашего ответа, с awk. как говорится, работает - не чини :)

user2141046 23.11.2022 08:10

Предполагая, что последний экземпляр этой фразы находится далеко внизу файла, обработка файла сзади значительно повышает производительность, например, с помощью 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? Мы копируем все это в память, чтобы иметь возможность записать обратно. Если этого слишком много, скопируйте его во временный файл вместо памяти, затем используйте его оттуда и удалите файл.

Обратите внимание, что это редактирует входной файл на месте.

zdim 20.11.2022 00:35

Это не однострочник. Код кажется правильным (и я действительно предпочитаю редактирование на месте), но это не то, о чем я просил...

user2141046 20.11.2022 08:52

@user2141046 user2141046 Ну да ... Я только что удалил примечание об этом, которое у меня было в тексте, так как я считаю это немного неуместным в целом. (Кроме того, люди часто упоминают об этом только для того, чтобы выяснить, что это не имеет значения — и некоторые другие требования здесь неясны.) Этот код делает именно то, что просят, и максимально эффективен, и это может иметь значение для Gig- полтора файла. Но не стесняйтесь отбрасывать, если это имеет значение «однострочный» (его, конечно, можно сократить и превратить в программу командной строки, но, на мой взгляд, это было бы неуместно). Я надеюсь, что это все еще полезно для других.

zdim 20.11.2022 09:14

Я согласен и проголосовал за вас, несмотря ни на что. пусть это будет во благо :)

user2141046 20.11.2022 10:04

@ user2141046, Re "Это не однострочник", Конечно. Ничто не мешает вам поставить его в одну строку.

ikegami 20.11.2022 18:39

Поскольку вы хотите прочитать новые строки из файла:

$ 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 в моей среде, поэтому хочу предотвратить коллизии)

user2141046 20.11.2022 15:25

В каждом сценарии оболочки - в контексте ввода представляет stdin. Не используйте псевдоним для less (я не знал, что вы МОЖЕТЕ использовать псевдонимы символов!), иначе у вас возникнут проблемы.

Ed Morton 20.11.2022 18:06

Другие вопросы по теме