sed -i(インプレース編集)で移植性を実現するにはどうすればよいですか?


41

FreeBSDを実行している共有ホスティングであるサーバー用のシェルスクリプトを書いています。また、Linuxを実行しているPCでローカルにテストできるようにしたいと考えています。したがって、私はそれらを移植可能な方法で記述しようとしていますが、それsedを行う方法はありません。

私のウェブサイトの一部は生成された静的HTMLファイルを使用し、このsed行は各再生成の後に正しいDOCTYPEを挿入します:

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

sedLinux上のGNU で動作しますが、FreeBSD sed-iオプションの後の最初の引数がバックアップコピーの拡張であると想定しています。これは次のようになります。

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

しかし、GNUはsed順番に式が直後に続くことを期待します-i。(改行処理の修正も必要ですが、それはここですでに回答されています

もちろん、スクリプトのサーバーコピーにこの変更を含めることもできますが、バージョン管理にVCSを使用することは混乱します。完全に移植可能な方法でsedでこれを達成する方法はありますか?


指定した2つのsedスニペットは同じですが、タイプミスはありませんか?また、すぐにバックアップ拡張機能を提供するGNU sedを実行できます-i
-iruvar

ああ、これを見つけてくれてありがとう。質問を修正しました。2行目はsedでエラーになります。'1s/ ^ / <!DOCTYPE html> \ n / 'がファイルであると予想され、見つからないことを訴えます。

1
クロスリファレンス:Mac(BSD)とLinux on Stack Overflowの両方で動作するsedインプレースフラグ
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が内部で行うことを手動で実行します。これはもう1行のコードです。

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

2
GNU sed -iも含意してい-sます。そして、GNUをチェックする最も簡単な方法は、GNU の有効なnoopであるが他のどこでも失敗sedするsed vコマンドを使用することです。
mikeserv 14

上記のヒントに触発され、ここでは、本当に1をしたい人のためのシングルライン(醜い場合)ポータブル版をだそれはありませんけれども産卵Aサブシェル:sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file"それはGNUのsedはない場合、それは後に2つの焦がす相場が続くスペースを挿入する-iので、そのBSDで動作します。GNU sedのみを取得し-iます。
イヴァンX

2
@IvanX vGNU sedをテストするためにコマンドの存在を使用するのは慎重です。FreeBSDがそれを実装することに決めた場合はどうなりますか?
ジル 'SO-悪であるのをやめる'

@Gilles公正なポイントですが、GNU sedのmanページでは、vがまさにその目的のためのものであると記述しています(GNU sedであり、他のものではないことを検出しています)。GNU sedでアクションを実行せず、BSD sedでエラーを発生させる(またはその逆)、-i自体を使用する以外のテストを考えることはできませんが、最初にダミーファイルを作成する必要があります。上記のsedのテストは問題ありませんが、インラインでは扱いにくいです。あなたが示唆するように、-iを完全に回避することは、確かに最も安全な賭けのように見えますが、sed vそれが存在する目的であることを考えると、私は大丈夫です。
イヴァンX

1
@lapo別の方法は、関数を定義することです。私の編集を参照してください。
ジル 'SO-悪であるのをやめる'

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コマンド。sed出力がバッファリングされるという事実を除いて、ファイルが4kを超える場合は機能しません。

基本的にはsed読み書き用の4Kのブロック上で動作します。挿入する行が4kより小さい場合、sedまだ読み取っていないブロックは上書きされません。

私はそれを頼りにしません。


あるべきecho '<!DOCTYPE html>'か「」引用符なしで脱出しました。
西暦

@AD良い点。私は通常、インタラクティブなbash / zshのバグを忘れる傾向があります。これは通常、自分で無効にするためです。
ステファンシャゼル

これは、静的で予測可能な名前の「一時ファイル」にリダイレクトするセキュリティホールが既に存在するかどうかを確認せずに広く開いていない、ここでの非常に少数の回答の1つです。これは受け入れられる答えだと思います。また、グループコマンドの使用の非常に素晴らしいデモンストレーションです。
ワイルドカード

3

Mac OS Xでも使用されるFreeBSD sed-e-iスイッチの後に、次の(正規表現)コマンドを正しく明確に定義および認識するためのオプションが必要です。

言い換えれば、sed -i -e ...FreeBSDとGNUの両方で動作するはずsedです。

より一般的には、FreeBSD sed -iがコマンドライン引数の解析中にFreeBSDの一部での混乱を避けるためにsed、いくつかの明示的なオプションまたはスイッチを必要とする後にバックアップ拡張機能を省略します。-ised

(ただし、sedインプレースファイル編集はファイルiノードの変更につながることに注意してください。「インプレース」ファイル編集を参照してください)。

(一般的なヒントとして、FreeBSDの最近のバージョンはsed持って-rGNUとの互換性を増大させるためにスイッチを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は、FreeBSDとの互換性のために(-rに加えて)-Eもサポートしていることに注意してください。-Eは、次のPOSIXバージョンで指定される可能性が高いため、そのことを忘れて、-rナンセンスであり、存在しないふりをする必要があります。
ステファンシャゼル

2

sed -i競合状態を可能な限り回避しながら、単一のファイルを移植可能にエミュレートするには:

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

ちなみに、これは、sed -iディレクトリおよびファイルのアクセス権に応じsed -iて、ユーザーが編集するアクセス権を持っていないファイルを上書きできる可能性があるという問題を処理します。

次のようなバックアップを行うこともできます。

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

2

ExモードでVimを使用できます。

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完全に準拠しています。明確な移植性のために私は使用します-cprintf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
ワイルドカード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.