sed: читати весь файл у просторі шаблону, не виходячи з ладу на однорядковому введенні


9

Читання цілого файлу в просторі шаблонів корисно для заміни нових рядків & c. і є багато випадків, які рекомендують таке:

sed ':a;N;$!ba; [commands...]'

Однак він не вдається, якщо вхід містить лише один рядок.

Наприклад, з двома вхідними рядками кожен рядок піддається команді підстановки:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

Але, з введенням однієї лінії, НЕ заміна не виконується:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

Як можна написати sedкоманду, щоб прочитати весь вхід одразу і не мати цієї проблеми?


Я відредагував ваше запитання так, щоб воно містило актуальне запитання. Ви можете зачекати на інші відповіді, якщо вам подобається, але в кінцевому підсумку позначте найкращу відповідь як прийняту (Дивіться кнопку труби зліва від відповіді, праворуч під кнопками зі стрілками вгору).
John1024

@ John1024 Дякую, добре мати приклад. Пошук подібних речей, як правило, нагадує мені, що "все не так", але я радий, що деякі з нас не здаються. :}
dicktyr

2
Є третій варіант! Використовуйте sed -zопцію GNU . Якщо ваш файл не має нульового значення, він читатиметься до кінця файлу! Знайдено з цього: stackoverflow.com/a/30049447/582917
CMCDragonkai

Відповіді:


13

Є всілякі причини, чому читання цілого файлу в просторі шаблонів може піти не так. Логічна проблема у питанні навколо останнього рядка є загальною. Це пов'язано з sedциклом ліній "Росія" - коли більше немає рядків і sedзустрічається EOF, він проходить - він припиняє обробку. І тому, якщо ви перебуваєте на останньому рядку, і ви даєте вказівку sedотримати інший, він зупиниться прямо там і більше не робитиме.

Це означає, що якщо вам дійсно потрібно прочитати цілий файл у просторі шаблонів, то, мабуть, варто все-таки розглянути інший інструмент. Справа в тому, що sedце однойменний редактор потоків - він призначений для роботи рядка - або логічного блоку даних - за раз.

Є багато подібних інструментів, які краще оснащені для обробки повних файлових блоків. edі ex, наприклад, можна зробити багато чого, що sedможна зробити, і з подібним синтаксисом - і багато іншого крім того, - але замість того, щоб працювати лише на вхідному потоці, перетворюючи його на вихід, як sedі вони, вони також підтримують тимчасові файли резервного копіювання у файловій системі . Їх робота буферизована на диску, якщо це потрібно, і вони не виходять різко в кінці файлу (і, як правило, імплодують набагато рідше під напругою буфера) . Більше того, вони пропонують багато корисних функцій, які sedне мають такого роду, які просто не мають сенсу в потоковому контексті - наприклад, позначки рядків, скасування, названі буфери, приєднання тощо.

sedОсновна сила - це його здатність обробляти дані, як тільки вони їх читають - швидко, ефективно та в потоці. Коли ви робите файл, ви викидаєте це, і ви, як правило, стикаєтеся з проблемами кращого регістру, як, наприклад, згадана вами проблема останнього рядка, і перевищення буфера, і бездоганна продуктивність - оскільки дані, які він аналізує, зростають у довжину, час обробки двигуна regexp при перерахуванні збігів зростає експоненціально .

Що стосується останнього пункту, до речі: хоча я розумію, що цей приклад s/a/A/g, швидше за все, є лише наївним прикладом і, мабуть, не є власне сценарієм, який ви хочете зібрати для введення, можливо, вам варто поцікавитися, аби ознайомитись y///. Якщо ви часто gвиявляєте, що замінюєте одного символу на іншого, то це yможе бути дуже корисним для вас. Це перетворення на відміну від заміни і відбувається набагато швидше, оскільки не передбачає повторного відтоку. Цей останній пункт також може бути корисним при спробі збереження та повторення порожніх //адрес, оскільки це не впливає на них, але може на них вплинути. У будь-якому випадку, y/a/A/є більш простий засіб здійснити те саме - і підміна можлива так само, як:y/aA/Aa/ який би міняв усі верхні / малі регістри, як на лінії один для одного.

Також слід зауважити, що поведінка, яку ви описуєте, насправді не є тією, що повинна так чи інакше відбуватися.

З GNU info sedв розділі ЗВІТНІ ЗВІТНІ ЧАСТИ :

  • N команда в останньому рядку

    • Більшість версій sedвиходу без нічого надрукувати, коли Nкоманда видана в останньому рядку файлу. GNU sedдрукує простір шаблону перед виходом, якщо, звичайно, -nне було вказано командний перемикач. Цей вибір за задумом.

    • Наприклад, поведінка в sed N foo barзалежності від того, чи має foo парне чи непарне число рядків. Або, коли ви пишете сценарій, щоб прочитати наступні рядки після відповідності шаблону, традиційні реалізації програми sedзмусять вас написати щось на зразок, /foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }а не просто /foo/{ N;N;N;N;N;N;N;N;N; }.

    • У будь-якому випадку, найпростішим способом вирішення є використання $d;Nв скриптах, що покладаються на традиційну поведінку, або встановлення POSIXLY_CORRECTзмінної на не порожнє значення.

POSIXLY_CORRECTМінлива оточення згадується , тому що POSIX специфицирует , що якщо sedзустрічає EOF при спробі Nвін повинен кинути курити без виходу, але версія GNU навмисно порушує зі стандартом в цьому випадку. Зауважте також, що навіть як поведінка виправдана вище припущення, що випадок помилки є одним з редагування потоків - не пригнічуючи весь файл в пам'ять.

У стандартних визначає N«и поведінку , таким чином:

  • N

    • Додайте наступний рядок введення, за вирахуванням його закінчувальної \nлінії виходу, до простору шаблону, використовуючи вбудовану \nлінію ewline для відокремлення доданого матеріалу від вихідного матеріалу. Зауважте, що номер поточного рядка змінюється.

    • Якщо наступний рядок введення недоступний, Nкомандне дієслово повинно розгалужуватися до кінця сценарію та вийти з нього, не запускаючи новий цикл або копіюючи простір шаблону на стандартний вихід.

У цій замітці є деякі інші GNU-ізми, продемонстровані у питанні, зокрема використання дужок :, bранчо та {функціональних контекстів }. Як правило, будь- sedяка команда, яка приймає довільний параметр, має розмежувати на \newline у ​​сценарії. Отже команди ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

... дуже ймовірно, що вони будуть виконуватись помилково, залежно від того, sedщо їх читає. Портативно їх слід записати:

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

Те ж саме справедливо і для r, w, t, a, i, і c (і , можливо , трохи більше , що я забуваю в даний момент) . Майже в кожному випадку вони також можуть бути написані:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... де новий -eоператор xecution стоїть для \nроздільника ewline. Тож там, де infoтекст GNU передбачає, що традиційна sedреалізація змусить вас це зробити :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... це швидше повинно бути ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

... звичайно, це теж не вірно. Писати сценарій таким чином - трохи нерозумно. Є набагато більш прості засоби зробити те саме, як:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... який друкує:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... тому що tкоманда est - як і більшість sedкоманд - залежить від циклу рядків, щоб оновити його регістр повернення, і тут цикл рядків дозволено виконувати більшу частину роботи. Це ще один компроміс, який ви робите, коли ви робите файли - цикл рядків більше не оновлюється, і так багато тестів будуть вести себе ненормально.

Наведена вище команда не ризикує перевиконати вхід, оскільки вона просто виконує прості тести, щоб перевірити, що вона читає під час її читання. Зі Hстарими всі рядки додаються до місця утримування, але якщо рядок відповідає, /foo/він замінює hстарий пробіл. Далі буфери xзмінюються, і s///робиться спроба умовного введення, якщо вміст буфера відповідає //останньому адресованому шаблону. Іншими словами, //s/\n/&/3pнамагається замінити третій новий рядок у просторі утримування та надрукувати результати, якщо простір утримування наразі відповідає /foo/. Якщо це tуспішно, сценарій відгалужується до мітки not delete - що робить lдуб і завершує сценарій.

У випадку, якщо /foo/і третій новий рядок не можуть бути зібрані разом у просторі утримування, тоді //!gвін замінить буфер, якщо /foo/він не збігається, або, якщо він збігається, він замінить буфер, якщо \newline не збігається (тим самим замінюючи /foo/на себе) . Цей невеликий витончений тест утримує буфер від заповнення без необхідності на тривалих розтягненнях /foo/і забезпечує процес залишається швидким, оскільки вхід не накопичується. Після цього у випадку «не» /foo/або « //s/\n/&/3pпомилка» буфери знову поміняються, і кожен рядок, але останній, видаляється.

Останній - останній рядок $!d- це проста демонстрація того, як можна створити sedсценарій зверху вниз, щоб легко обробляти декілька справ. Коли ваш загальний метод полягає в тому, щоб вирізати небажані випадки, починаючи з найбільш загальних і працюючи над самими конкретними, тоді крайові випадки можна легше обробляти, тому що їм просто дозволено потрапляти до кінця сценарію з іншими потрібними даними та коли це все, що вам залишилося, лише ті дані, які ви хочете. Однак, витягнути такі крайові корпуси із замкнутого циклу може бути набагато складніше.

І ось ось останнє, що я маю сказати: якщо ви дійсно повинні витягнути цілий файл, то ви можете стояти, щоб зробити трохи менше роботи, покладаючись на цикл ліній, щоб зробити це за вас. Зазвичай ви будете використовувати Nвнутр і nзовн для випереджаючого перегляду - тому що вони просуваються вперед циклу лінії. Замість того, щоб надмірно реалізувати замкнутий цикл у циклі - оскільки sedцикл ліній все одно просто простий цикл читання - якщо ваша мета полягає лише в тому, щоб збирати вхід без розбору, то це, мабуть, простіше:

sed 'H;1h;$!d;x;...'

... який збере весь файл або спробує перебрати.


побічна примітка про Nта останню поведінку рядка ...

в той час як у мене немає інструментів, доступних мені для тестування, врахуйте, що Nпід час читання та редагування на місці поводиться інакше, якщо редагований файл - це файл сценарію для наступного перегляду.


1
Поставити безумовне Hперше - це прекрасно.
jthill

@mikeserv Дякуємо за ваш внесок. Я бачу потенційну користь у підтримці циклу ліній, але як це менше роботи?
диктер

@dicktyr добре, що синтаксис займає деякі ярлики, :a;$!{N;ba}як я вже згадував вище - простіше використовувати стандартну форму в довгостроковій перспективі, коли ви намагаєтеся запускати регулярні вирази на незнайомих системах. Але це було не так, що я мав на увазі: Ви реалізуєте замкнутий цикл - ви не можете так легко потрапити в середину цього, коли хочете, натомість, розгалужуючи - обрізаючи небажані дані - і дозволяючи циклу відбуватися. Це як річ зверху вниз - все, що sedвідбувається, є прямим результатом того, що він щойно зробив. Можливо, ви бачите це інакше - але якщо ви спробуєте, ви можете знайти сценарій простіше.
mikeserv

11

Це не вдається, тому що Nкоманда надходить до відповідності шаблону $!(не останнього рядка), а sed закінчується перед будь-якою роботою:

N

Додайте нову лінію до простору шаблонів, а потім додайте наступний рядок вводу до простору шаблону. Якщо входу більше немає, sed виходить, не обробляючи більше команд .

Це можна легко зафіксувати і для роботи з однорядковим введенням (і, в будь-якому випадку, бути більш зрозумілим), просто згрупувавши команди Nта bкоманди за шаблоном:

sed ':a;$!{N;ba}; [commands...]'

Він працює наступним чином:

  1. :a створити мітку під назвою "a"
  2. $! якщо не останній рядок, то
  3. Nдодайте наступний рядок до простору шаблону (або закрийте, якщо немає наступного рядка) та baвідгалуження (перейти до) мітки "a"

На жаль, він не портативний (оскільки він покладається на розширення GNU), але наступна альтернатива (запропонована @mikeserv) є портативною:

sed 'H;1h;$!d;x; [commands...]'

Я розмістив цю інформацію тут, оскільки не знайшов іншої інформації в іншому місці, і хотів зробити її доступною, щоб інші могли уникнути проблем із широким розповсюдженням :a;N;$!ba;.
диктер

Дякуємо за публікацію! Пам’ятайте, що також прийняти власну відповідь. Вам просто потрібно почекати деякий час, перш ніж система дозволить вам це зробити.
тердон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.