Є всілякі причини, чому читання цілого файлу в просторі шаблонів може піти не так. Логічна проблема у питанні навколо останнього рядка є загальною. Це пов'язано з 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в розділі ЗВІТНІ ЗВІТНІ ЧАСТИ :
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під час читання та редагування на місці поводиться інакше, якщо редагований файл - це файл сценарію для наступного перегляду.