Як я можу досягти портативності за допомогою sed -i (на місці редагування)?


41

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

Частина мого веб-сайту використовує генеровані статичні файли HTML, і цей рядок sed вставляє правильний DOCTYPE після кожного регенерації:

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

Він працює з GNU sedна Linux, але FreeBSD sedочікує, що перший аргумент після -iопції буде розширенням для резервної копії. Ось як це виглядатиме:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

Однак GNU, sedу свою чергу, очікує, що цей вираз буде застосований відразу після -i. (Також потрібні виправлення за допомогою обробки нового рядка, але про це вже відповіли тут )

Звичайно, я можу включити цю зміну в свою копію сценарію на сервері, але це могло б заплутатися, тобто моє використання VCS для версії версій. Чи є спосіб досягти цього за допомогою sed на повністю портативний спосіб?


Два фрагменти sed, які ви надали, є однаковими, ви впевнені, що немає друку? Крім того, я можу виконати GNU sed, поставляючи розширення резервного копіювання відразу після-i
iruvar

Дю, дякую, що помітили це. Я виправив своє запитання. Другий рядок призводить до помилки в моєму sed, він очікує, що файл '1s / ^ / <! DOCTYPE html> \ n /' є файлом, і скаржиться, що не може його знайти.
Червоний

1
Перехресне посилання: прапор sed-place, який працює як на Mac (BSD), так і на Linux у Stack Overflow.
MvG

Відповіді:


40

GNU sed приймає необов'язкове розширення після -i. Розширення повинно бути в одному аргументі, без пробілу. Цей синтаксис також працює на BSD sed.

sed -i.bak -e '…' SOMEFILE

Зауважте, що на BSD -iтакож змінюється поведінка, коли є кілька вхідних файлів: вони обробляються незалежно (так, наприклад, $відповідає останньому рядку кожного файлу). Також це не працюватиме на BusyBox.

Якщо ви не хочете використовувати резервні файли, ви можете перевірити, яка версія sed доступна.

case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
esac
"$@" -e '…' "$file"

Або в якості альтернативи, щоб уникнути розхитування позиційних параметрів, визначте функцію.

case $(sed --help 2>&1) in
  *GNU*) sed_i () { sed -i "$@"; };;
  *) sed_i () { sed -i '' "$@"; };;
esac
sed_i -e '…' "$file"

Якщо ви не хочете турбуватися, використовуйте Perl.

perl -i -pe '…' "$file"

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

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"

2
GNU sed -iтакож має на увазі -s. І найпростіший спосіб перевірити наявність GNU sed- це sed vкоманда, яка є дійсною noop для GNU, але не працює в будь-якому іншому місці.
mikeserv

Натхненний вищезазначеними порадами, ось однополосна (якщо некрасива) портативна версія для тих, хто дійсно хоче її, хоча вона ініціює нижню частину: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"Якщо це не GNU sed, він вставляє пробіл, а потім два лайни, після -iчого працює над BSD. GNU sed отримує тільки -i.
Іван X

2
@IvanX Я з обережністю використовую присутність vкоманди для тестування на GNU sed. Що робити, якщо FreeBSD вирішив її впровадити?
Жил "ТАК - перестань бути злим"

@Gilles Справедливий пункт, але сторінка man для GNU sed описує v як саме для цієї мети (виявляючи, що це GNU sed, а не щось інше), тож можна сподіватися, що * BSD це честь. Я не можу придумати ще один тест, який, не вживаючи дій на GNU sed, при цьому викликає помилку на BSD sed (або навпаки), крім використання самого -i, але для цього потрібно було б створити фіктивний файл спочатку. Ваш тест на sed вище в порядку, але важкий для inline. Уникання - я цілком, як ви пропонуєте, звичайно здається найбезпечнішою ставкою, але я все в порядку з використанням, sed vякщо це є його метою для існуючих.
Іван X

1
@lapo Альтернативою є визначення функції, див. мою редагування.
Жил "ТАК - перестань бути злим"

10

Якщо ви не знайдете хитрості, щоб зробити sedприємну гру, спробуйте:

  1. Не використовувати -i:

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
  2. Використовуйте Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"

8

ред

Ви завжди edможете додати рядок до наявного файлу.

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html

Деталі

Біти навколо <!DOCTYPE html>команди - це команди, які edвказують йому додати цей рядок у файл my.html.

sed

Я вважаю, що ця команда в sedтакож може бути використана:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv

Зрештою я вдався до Perl, але використання Ed - це хороша альтернатива, яка не користується популярністю серед користувачів, схожих на Unix, як це належить.
Червоний

@Red - рада почути, що ти вирішив свою проблему. Так, я цього раніше не бачив, гуглінг повернув його, і це насправді здавалося найбільш портативним, вдалим способом зробити це.
slm

7

Ви також можете зробити вручну, що perl -iробиться під кришкою:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file

Мовляв perl -i, немає резервної копії, і як і більшість рішень, наведених тут, будьте обережні, це може вплинути на дозволи, право власності на файл і може перетворити посилання на звичайний файл.

З:

sed '1i\
<!DOCTYPE html>' file 1<> file

sedбуде перезаписати файл над собою, так що це не вплине на право власності та дозволи або символьні посилання. Він працює з GNU, sedоскільки sed, як правило, прочитав буфер, повний даних з file(4k в моєму випадку), перш ніж перезаписати його iкомандою. Це не спрацювало, якби файл перевищував 4k, за винятком того факту, який sedтакож буферизує його вихід.

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

Я б на це не розраховував.


Повинно бути echo '<!DOCTYPE html>'або уникнути без "" лапок.
AD

@AD Добрий момент. Я, як правило, забуваю про цю помилку ^ Wfeature інтерактивного bash / zsh, оскільки я взагалі відключаю її для себе.
Стефан Шазелас

Це одна з дуже небагатьох відповідей тут , які не мають навстіж отвори безпеки перенаправлення на «тимчасовий файл» зі статичним, передбачуваним ім'ям без перевірки , якщо вона вже існує. Я вважаю, що це має бути прийнятою відповіддю. Дуже приємна демонстрація використання команд групи.
Wildcard

3

FreeBSD sed , який використовується і в Mac OS X, потребує -eопції після -iперемикання, щоб визначити та розпізнати наступну команду (регулярний вираз) правильно та однозначно.

Іншими словами, sed -i -e ...слід працювати з FreeBSD та GNU sed.

Більш загально, опускання розширення резервного копіювання після FreeBSD sed -iвимагає явної sedопції або перемикання слідом за тим, -iщоб уникнути плутанини частини FreeBSD sedпід час аналізу її аргументів командного рядка.

(Однак зауважте, що sedмісцеві редагування файлів призводять до змін інода файлів, див. "На місці" редагування файлів ).

(Як загальний натяк, останні версії FreeBSD sedмають -rперемикач для підвищення сумісності з GNU sed).

echo a > testfile.txt
ls -li testfile.txt
#gsed -i -e 's/a/A/' testfile.txt
#bsdsed -i 's/a/A/' testfile.txt  # does not work
bsdsed -i -e 's/a/A/' testfile.txt
ls -li testfile.txt
cat testfile.txt

5
Ні, bsdsed -i -e 's/a/A/'це не місцеве редагування, це редагування із збереженням оригіналу суфіксом "-e" ( testfile.txt-e).
Стефан Шазелас

зауважте, що GNU sed також підтримує -E (на додаток до -r) для сумісності з FreeBSD. -E, ймовірно, буде вказано у наступній версії POSIX, тому всі ми повинні забувати про це -r безглуздо і робити вигляд, що його ніколи не було.
Stéphane Chazelas

2

Емуляція sed -iодного файлу портативно, максимально уникаючи перегонових умов:

sed 'script' <<FILE >file
$(cat file)
FILE

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

Ви також можете робити резервні копії:

sed 'script' <<FILE >file
$(tee file.old <file)
FILE

2

Ви можете використовувати Vim в режимі Ex:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 виберіть перший рядок

  2. i вставити текст і новий рядок

  3. x зберегти і закрити

Або навіть, як у коментарях, звичайний старий стандарт ex :

printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file 

Тут немає нічого конкретного для Vim. Це повністю відповідає специфікаціям POSIX дляex , за винятком того, що для підтримки декількох прапорів реалізація не потрібна-c . Для певної портативності я б використавprintf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
Wildcard
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.