Jak mogę uzyskać przenośność za pomocą sed -i (edycja na miejscu)?

41

Piszę skrypty powłoki dla mojego serwera, który jest hostem współdzielonym z systemem FreeBSD. Chcę też móc przetestować je lokalnie na komputerze z systemem Linux. Dlatego staram się pisać je w przenośny sposób, ale sednie widzę sposobu, aby to zrobić.

Część mojej strony używa generowanych statycznych plików HTML, a ta linia sed wstawia poprawny DOCTYPE po każdej regeneracji:

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

Działa z GNU sedw systemie Linux, ale FreeBSD sedoczekuje, że pierwszym argumentem po -iopcji będzie rozszerzenie kopii zapasowej. Tak to wyglądałoby:

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

Jednak GNU sedz kolei oczekuje, że wyrażenie pojawi się natychmiast po nim -i. (Wymaga to również poprawek z obsługą nowego wiersza, ale na to już tutaj odpowiedziano )

Oczywiście mogę dołączyć tę zmianę do mojej kopii skryptu na serwerze, ale to by popsuło, tj. Moje użycie VCS do wersji. Czy istnieje sposób, aby to osiągnąć za pomocą sed w pełni przenośny sposób?

Czerwony
źródło
Dwa podane fragmenty sed są identyczne, czy na pewno nie ma literówki? Ponadto jestem w stanie wykonać GNU sed dostarczający rozszerzenie kopii zapasowej zaraz po-i
iruvar 29.09.2013
Dzięki, że to zauważyłeś. Naprawiłem moje pytanie. Drugi wiersz powoduje błąd w moim sed, oczekuje on, że „1s / ^ / <! DOCTYPE html> \ n /” będzie plikiem i narzeka, że ​​nie może go znaleźć.
Red

Odpowiedzi:

40

GNU sed akceptuje opcjonalne rozszerzenie po -i. Rozszerzenie musi być w tym samym argumencie bez spacji. Ta składnia działa również na BSD sed.

sed -i.bak -e '…' SOMEFILE

Zauważ, że w BSD -izmienia również zachowanie, gdy istnieje wiele plików wejściowych: są one przetwarzane niezależnie (więc np. $Dopasowuje ostatni wiersz każdego pliku). Również to nie będzie działać w BusyBox.

Jeśli nie chcesz używać plików kopii zapasowych, możesz sprawdzić, która wersja sed jest dostępna.

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

Lub alternatywnie, aby uniknąć blokowania parametrów pozycji, zdefiniuj funkcję.

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

Jeśli nie chcesz się tym przejmować, użyj Perla.

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

Jeśli chcesz napisać przenośny skrypt, nie używaj -igo - nie ma go w POSIX. Rób ręcznie to, co robi sed pod maską - to tylko jeden wiersz kodu.

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"
Gilles „SO- przestań być zły”
źródło
2
GNU sed -irównież implikuje -s. Najłatwiejszym sposobem sprawdzenia GNU sedjest sed vużycie polecenia, które jest prawidłowym noop dla GNU, ale zawodzi wszędzie indziej.
mikeserv
Zainspirowany powyższymi wskazówkami, oto jednowierszowa (jeśli brzydka) przenośna wersja dla tych, którzy naprawdę tego chcą, choć spawnuje podpowłokę: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"jeśli nie jest to GNU sed, wstawia spację, po której następują dwa pojedyncze cytaty -i, aby działa na BSD. GNU sed dostaje tylko -i.
Ivan X
2
@IvanX Nieufnie używam obecności vpolecenia do testowania GNU sed. Co jeśli FreeBSD zdecyduje się go wdrożyć?
Gilles 'SO - przestań być zły'
@Gilles Fair point, ale strona podręcznika GNU sed opisuje v jako dokładnie do tego celu (wykrywanie, że jest to GNU sed, a nie coś innego), więc można mieć nadzieję, że * BSD to honoruje. Nie mogę pomyśleć o innym teście, odręcznie, który nie podejmuje żadnych działań na GNU sed, powodując błąd na BSD sed (lub odwrotnie), inny niż użycie samego -i, ale wymagałoby to najpierw utworzenia pliku zastępczego. Twój test dla sed powyżej jest OK, ale nieporęczny dla inline. Unikanie całkowicie całkowicie, jak sugerujesz, z pewnością wydaje się być najbezpieczniejszym zakładem, ale jestem w porządku, sed vbiorąc pod uwagę, że taki jest cel istnienia.
Ivan X
1
@lapo Alternatywą jest zdefiniowanie funkcji, zobacz moją edycję.
Gilles 'SO - przestań być zły'
10

Jeśli nie znajdziesz sposobu na uprzyjemnienie sedgry, możesz spróbować:

  1. Nie używaj -i:

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
  2. Użyj Perla

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"
terdon
źródło
8

wyd

Zawsze możesz użyć, edaby dodać linię do istniejącego pliku.

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

Detale

Bity wokół <!DOCTYPE html>poleceń są poleceniami, by edpolecił mu dodanie tej linii do pliku my.html.

sed

Wierzę, że tego polecenia sedmożna również użyć:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv
slm
źródło
W końcu uciekłem się do Perla, ale używanie ed jest dobrą alternatywą, która nie jest popularna wśród użytkowników uniksowych, tak jak powinna.
Red
@ Red - cieszę się, że rozwiązałeś problem. Tak, nie widziałem tego wcześniej, Google go podkręcił i faktycznie wydawało się to najbardziej przenośnym, trafnym sposobem na zrobienie tego.
slm
7

Możesz także zrobić ręcznie to, co perl -irobi pod maską:

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

Jak perl -i, nie ma kopii zapasowej, i jak większość rozwiązań podanych tu uwaga może wpływać na uprawnienia własność pliku i może okazać się dowiązania do zwykłego pliku.

Z:

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

sednadpisze sam plik, więc nie wpłynie na własność i uprawnienia ani dowiązania symboliczne. Działa z GNU, sedponieważ sedzwykle przed odczytaniem bufora pełnego danych file(4k w moim przypadku) i. To nie zadziałałoby, gdyby plik był większy niż 4k, z wyjątkiem tego, że sedbuforuje również jego dane wyjściowe.

Zasadniczo seddziała na blokach 4k do czytania i pisania. Jeśli linia do wstawienia jest mniejsza niż 4k, sednigdy nie zastąpi bloku, którego jeszcze nie przeczytała.

Nie liczyłbym na to.

Stéphane Chazelas
źródło
Powinien być echo '<!DOCTYPE html>'lub uciekł bez cudzysłowów.
AD
@AD Dobra uwaga. Zazwyczaj zapominam o tym błędzie ^ Fakeature interaktywnego bash / zsh, ponieważ generalnie wyłączam go dla siebie.
Stéphane Chazelas
Jest to jeden z bardzo nielicznych odpowiedzi tutaj, że nie ma szeroko otwarte dziury bezpieczeństwa przekierowanie do pliku „temp” ze statycznym, przewidywalny nazwy bez sprawdzania, jeśli już istnieje. Uważam, że powinna to być zaakceptowana odpowiedź. Bardzo ładna demonstracja użycia poleceń grupowych.
Wildcard,
3

FreeBSD sed , który jest również używany w systemie Mac OS X, potrzebuje -eopcji po -iprzełączniku, aby poprawnie zdefiniować i rozpoznać następujące polecenie (wyrażenie regularne) poprawnie i jednoznacznie.

Innymi słowy, sed -i -e ...powinien działać zarówno z FreeBSD, jak i GNU sed.

Mówiąc bardziej ogólnie, pominięcie rozszerzenia kopii zapasowej po FreeBSD sed -iwymaga jakiejś wyraźnej sedopcji lub przełączenia się, -iaby uniknąć pomyłek ze strony FreeBSD sedpodczas analizowania argumentów wiersza poleceń.

(Należy jednak pamiętać, że sededycje plików w miejscu prowadzą do zmian i-węzłów plików, patrz edycja plików w miejscu ).

(Jako ogólna wskazówka, najnowsze wersje FreeBSD sedmają -rprzełącznik zwiększający kompatybilność z 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
Carlo
źródło
5
Nie, niebsdsed -i -e 's/a/A/' jest edycją lokalną, lecz edycją z zapisaniem oryginału z przyrostkiem „-e” ( ). testfile.txt-e
Stéphane Chazelas
zauważ, że GNU sed obsługuje także -E (oprócz -r) dla kompatybilności z FreeBSD. -E prawdopodobnie zostanie określone w następnej wersji POSIX, więc wszyscy powinniśmy zapomnieć o tym -r nonsensie i udawać, że nigdy nie istniał.
Stéphane Chazelas
2

Aby emulować sed -ijeden plik przenośnie, unikając w jak największym stopniu warunków wyścigu:

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

Nawiasem mówiąc, rozwiązuje to również możliwy problem, który sed -iwprowadza, w zależności od uprawnień do katalogu i plików, sed -imoże umożliwić użytkownikowi zastąpienie pliku, którego ten użytkownik nie ma uprawnień do edycji.

Możesz także wykonywać kopie zapasowe, takie jak:

sed 'script' <<FILE >file
$(tee file.old <file)
FILE
mikeserv
źródło
2

Możesz używać Vima w trybie Ex:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 wybierz pierwszą linię

  2. i wstaw tekst i znak nowej linii

  3. x Zapisz i zamknij

Lub nawet, jak w komentarzach, zwykły stary standard ex :

printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file 
Steven Penny
źródło
Nie ma tu nic specyficznego dla Vima. Jest to w pełni zgodne ze specyfikacjami POSIX dlaex , z tym wyjątkiem, że implementacje nie są wymagane do obsługi wielu -cflag. Dla pewnej przenośności printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
użyłbym