Come posso raggiungere la portabilità con sed -i (editing sul posto)?


42

Sto scrivendo script shell per il mio server, che è un hosting condiviso con FreeBSD. Voglio anche essere in grado di testarli localmente, sul mio PC con Linux. Quindi, sto provando a scriverli in modo portatile, ma con sednon vedo alcun modo per farlo.

Parte del mio sito Web utilizza file HTML statici generati e questa linea sed inserisce DOCTYPE corretto dopo ogni rigenerazione:

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

Funziona con GNU sedsu Linux, ma FreeBSD si sedaspetta che il primo argomento dopo l' -iopzione sia l'estensione per la copia di backup. Ecco come sarebbe:

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

Tuttavia, GNU seda sua volta si aspetta che l'espressione segua immediatamente dopo -i. (Richiede anche correzioni con la gestione di newline, ma è già stata fornita una risposta qui )

Ovviamente posso includere questa modifica nella copia del mio server dello script, ma ciò rovinerebbe il mio uso di VCS per il controllo delle versioni. C'è un modo per raggiungere questo obiettivo con sed in un modo completamente portatile?


I due frammenti di sed che hai fornito sono identici, sei sicuro che non ci sia un refuso? Inoltre, sono in grado di eseguire GNU sed fornendo l'estensione di backup subito dopo-i
iruvar,

Duh, grazie per averlo individuato. Ho risolto la mia domanda. La seconda riga provoca errori nel mio sed, si aspetta che '1s / ^ / <! DOCTYPE html> \ n /' sia un file e si lamenta che non lo trova.
Rosso

1
Riferimenti incrociati: flag sul posto sed che funziona sia su Mac (BSD) che Linux su Stack Overflow.
MvG

Risposte:


41

GNU sed accetta un'estensione opzionale dopo -i. L'estensione deve trovarsi nello stesso argomento senza spazi intermedi. Questa sintassi funziona anche su BSD sed.

sed -i.bak -e '…' SOMEFILE

Notare che su BSD -icambia anche il comportamento in presenza di più file di input: vengono elaborati in modo indipendente (quindi ad es. $Corrisponde all'ultima riga di ciascun file). Inoltre, questo non funzionerà su BusyBox.

Se non si desidera utilizzare i file di backup, è possibile verificare quale versione di sed è disponibile.

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

In alternativa, per evitare di ostruire i parametri posizionali, definire una funzione.

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

Se non vuoi disturbarti, usa Perl.

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

Se vuoi scrivere uno script portatile, non usare -i- non è in POSIX. Fai manualmente ciò che sed fa sotto il cofano: è solo un'altra riga di codice.

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

2
GNU sed -iimplica anche -s. E il modo più semplice per verificare la presenza di una GNU sedè con il sed vcomando che è un noop valido per GNU ma fallisce ovunque.
Mikeserv,

Ispirato ai suggerimenti di cui sopra, ecco una versione portatile a riga singola (se brutta) per coloro che ne vogliono davvero uno, anche se genera una subshell: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"Se non è GNU sed, inserisce uno spazio seguito da due virgolette successive in -imodo che funziona su BSD. GNU sed ottiene solo -i.
Ivan X,

2
@IvanX Sono diffidente nell'utilizzare la presenza del vcomando per testare GNU sed. E se FreeBSD avesse deciso di implementarlo?
Gilles 'SO- smetti di essere malvagio' il

@Gilles Fair point, ma la pagina man di GNU sed descrive v come esattamente per quello scopo (rilevando che è GNU sed e non qualcos'altro), quindi si spera che * BSD lo onori. Non riesco a pensare a un altro test, a dir poco, che non esegue alcuna azione su GNU sed, causando un errore su BSD sed (o viceversa), oltre all'utilizzo di -i stesso, ma richiederebbe prima di creare un file fittizio. Il test per sed sopra è OK ma ingombrante per in linea. Evitare del tutto -i, come suggerisci, sembra certamente la scommessa più sicura, ma sono d'accordo con l'utilizzo sed vdato che è il suo scopo per esistere.
Ivan X,

1
@lapo Un'alternativa è definire una funzione, vedi la mia modifica.
Gilles 'SO-smetti di essere malvagio' il

10

Se non trovi un trucco per rendere sedpiacevole il gioco, puoi provare:

  1. Non usare -i:

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
  2. Usa Perl

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

8

Ed

È sempre possibile utilizzare edper anteporre una riga a un file esistente.

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

Dettagli

I bit attorno a <!DOCTYPE html>sono comandi per indicare a edquest'ultimo di aggiungere quella linea al file my.html.

sed

Credo che questo comando in sedpossa anche essere usato:

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

Alla fine ho fatto ricorso a Perl, ma usare ed è una buona alternativa che non è popolare tra gli utenti di tipo Unix come dovrebbe essere.
Rosso

@Rosso - felice di sentire che hai risolto il problema. Sì, non l'avevo mai visto prima, cercavo su Google e in realtà sembrava il modo più portatile e adatto per farlo.
slm

7

Puoi anche fare manualmente ciò che perl -ifa sotto il cofano:

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

Ad esempio perl -i, non esiste alcun backup e, come la maggior parte delle soluzioni fornite qui, attenzione che potrebbe influenzare le autorizzazioni, la proprietà del file e potrebbe trasformare un link simbolico in un file normale.

Con:

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

sedsovrascriverebbe il file su se stesso, quindi non influirebbe sulla proprietà e le autorizzazioni o i collegamenti simbolici. Funziona con GNU sedperché sedin genere avrà letto un buffer pieno di dati da file(4k nel mio caso) prima di sovrascriverlo con il icomando. Non funzionerebbe se il file fosse più di 4k, tranne per il fatto che sedbuffer anche il suo output.

Fondamentalmente sedfunziona su blocchi di 4k per leggere e scrivere. Se la riga da inserire è più piccola di 4k, sednon sovrascriverà mai un blocco che non ha ancora letto.

Non ci conterei però.


Dovrebbe essere echo '<!DOCTYPE html>'o fuggito senza "" virgolette.
DC

@AD buon punto. Tendo a dimenticarmi di quel bug ^ Funzionalità di interattivo bash / zsh poiché generalmente lo disabilito per me stesso.
Stéphane Chazelas,

Questo è uno dei molto poche risposte qui che non hanno un buco di sicurezza spalancata di reindirizzare ad un "file temporaneo" con uno statico, nome prevedibile senza controllare se esiste già. Credo che questa dovrebbe essere la risposta accettata. Molto bella dimostrazione dell'uso dei comandi di gruppo, anche.
Wildcard l'

3

FreeBSD sed , che viene utilizzato anche su Mac OS X, necessita -edell'opzione dopo lo -iswitch per definire e riconoscere il seguente comando (regex) in modo corretto e inequivocabile.

In altre parole, sed -i -e ...dovrebbe funzionare sia con FreeBSD che con GNU sed.

Più in generale, omettere l'estensione di backup dopo FreeBSD sed -irichiede alcune sedopzioni esplicite o passare a seguito -iper evitare confusione su parte di FreeBSD sedmentre analizza i suoi argomenti da riga di comando.

(Si noti, tuttavia, che sedle modifiche dei file sul posto comportano modifiche agli inode dei file, vedere Modifica "sul posto" dei file ).

(Come suggerimento generale, le recenti versioni di FreeBSD sedhanno il -rpassaggio per aumentare la compatibilità con 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
No, nonbsdsed -i -e 's/a/A/' è una modifica sul posto, è una modifica con il salvataggio dell'originale con un suffisso "-e" ( ). testfile.txt-e
Stéphane Chazelas,

nota che GNU sed supporta anche -E (oltre a -r) per la compatibilità con FreeBSD. -E è probabile che venga specificato nella prossima versione POSIX, quindi dovremmo tutti dimenticare questo -r non-senso e far finta che non sia mai esistito.
Stéphane Chazelas,

2

Per emulare sed -iun singolo file in modo portabile evitando il più possibile le condizioni di gara:

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

A proposito, questo gestisce anche il possibile problema che sed -iintroduce che, a seconda delle autorizzazioni di directory e file, sed -ipotrebbe consentire a un utente di sovrascrivere un file che l'utente non dispone delle autorizzazioni per la modifica.

Puoi anche fare backup come:

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

2

Puoi usare Vim in modalità Ex:

ex -sc '1i|<!DOCTYPE html>' -cx file
  1. 1 seleziona la prima riga

  2. i inserisci testo e newline

  3. x salva e chiudi

O anche, come nei commenti, semplice vecchio standard ex :

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

Non c'è niente di specifico per Vim qui. Ciò è pienamente conforme alle specifiche POSIX perex , tranne per il fatto che le implementazioni non sono necessarie per supportare più -cflag. Per una portabilità definitiva usereiprintf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
Wildcard il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.