テキストファイルから最初の数行を効率的に削除する

テキストファイルから最初の数行を効率的に削除する
  • head/はtailほぼファイル全体を繰り返す必要があります(引数として提供する行の位置によって異なります)。次に、結果を新しいファイルにコピーし、古いファイルを削除します。

  • ファイル全体が繰り返されるかどうかはわかりませんが、sed結果を新しいファイルにコピーして古いファイルを削除する必要があります。-i(その場で)バックグラウンドに一時ファイルを作成するため、同じことが当てはまります。

ポインタをファイルの最初の行に移動し、目的の行に移動するとどうなりますか?

どうすればそのようなことができますか?ぜひCにしなければなりませんか?別の方法がありますか?

馬になる? ?私は間違っていますか?それではなぜ?

答え1

ポインタをファイルの最初の行に移動し、目的の行に移動するとどうなりますか?

「ファイルの最初の行へのポインタ」のようなものがないからです。

ファイル変更の基本的な操作は、特定の範囲のバイトを含む(たとえば、一部を同じ長さのデータで置き換える)、追加(つまり、最後に追加)、切り捨て(つまり、最後から削除)です。

ほとんどのファイルシステムはファイルを固定サイズのブロックに保存しますが、最後のブロックは部分的にすることができます。変更によって変更中の項目のサイズが変更された場合、変更が完了しない場合、または変更によってデータが整数ブロック数だけ移動しない限り、データはその場で変更できません。ブロック全体にわたってデータを移動することは偶然の問題であり、これを実行するための広範なインターフェースはありません。

ファイルの先頭からデータを削除する最も効率的な方法は、アーカイブしたいデータを新しいファイルにコピーすることです。これが「何をすべきか」tail -n +42または「sed '41,$p'何をすべきか」です。

1最新のLinuxシステムには、ファイルの一部を削除するシステムコールがあります。fallocate(fd, FALLOC_FL_COLLAPSE_RANGE, …)、ユーティリティを介して呼び出すことができます。fallocate --collapse-range=…。そしてFALLOC_FL_INSERT_RANGE--insert-rangeただし、ブロックに制限されているため、テキストファイルにはほとんど役に立たず、すべてのファイルシステムで動作しません。

答え2

Gilesは私よりも一歩先んじていました。 「ファイルの最初の行へのポインタ」はありません。ファイルの最初の行(ファイルの先頭)は常にファイルの最初の文字です。 (この概念を認識するあいまいで別々のアプリケーションがあるかもしれませんが、システムレベルではそうではありません。)

あなたがすでに知っているもの:

次のコマンド

  • sed '1,6d' filename
  • sed -n '7,$p' filename
  • tail -n +7 filename

(他のバリエーションがある可能性があります)最初の6行を除くすべての行を作成します。filename標準出力として。 (もちろん、彼らはすべて文書を読みました。)これにより、

  • sed -n '1,6p' filename
  • sed '7,$d' filename
  • head -n 6 filename
  • sed '6q' filename

最初の6行を書き込みます。filename標準出力として。最初の 2 つはファイル全体を読み取ることも、そうでない場合もあります。

返品、

コマンドinput_file名>同じファイル名
で説明したように動作しません。「>」に関する警告

あなたが知らないかもしれないものは次のとおりです。

コマンドパラメータ    1<>ファイル名

開く予定ですfilename読み書きのため 切らず(割れず)。  だから、

sed '1,6d'ファイル名  1<>同じファイル名
あなたが探しているソリューションに向けた最初のステップかもしれません。これはおそらく削除したい最初のものと似ています。中サイズファイルの行は「所定の位置に」あります。他のファイルを作成せずにファイルを読み取り、同時に上書きします。もし中サイズ十分に小さい場合(または特に最初のバイト数中サイズ行が十分に小さい)、これはファイルの各ブロックを一度読み、各ブロックを一度書き込むことができ、これより良い方法はありません。

ただ最初ステップ?

このテストファイルを作成しました。

$猫-n foo
     1
     2ヤード
     3 エッジ
     4 jklmnop
     5qrstuvwxy
     6 z0123456789
     7ABCDEFGHIJKLM
     8 昔、ある淫乱な深夜に、わたしは力がなく、疲れて考えに浸った。
     9奇妙で好奇心が多く、忘れられた多くの知識の中で -
    10 わたしは眠くしようと頭をうなずいて、突然叩く音がした
    11.誰かが私の家のドアを少し叩くようです。
    12「私の家は私の家のドアを叩いた」私はささやいた。
    13. まさにそれだ。 」
    14クイックブラウン
    フォックスジャンプ15回
    16.怠惰な犬。昔々
    17.この退屈な真夜中

ファイルは、行の長さ(改行を含む)が次のように慎重に構成されています。2、4、6、8、10、12、14、63、57、63、58、62、63、16、18、20番号22。したがって、最初の6行には2 + 4 + 6 + 8 + 10 + 12 = 42バイトが含まれます。最後の2行には20+22バイトが含まれていますが、やはり(!)やはり42バイトです。 (合計ファイルサイズは504です。)だから、

$ls -l foo
-rw-r--r-- 1マイユーザー名マイグループ名504 5月18日 04:25 リッチ

$ sed '1,6d' foo 1<> foo

$ls -l foo
-rw-r--r-- 1マイユーザー名マイグループ名504 5月18日 04:32 金持ち

$猫-n foo
     1ABCDEFGHIJKLM
     2 昔、ある淫散な深夜に、私が力がなく疲れて考えていたとき
     3奇妙で好奇心が多く、忘れられた多くの知識の中で—
     4 わたしは頭をうなずいて眠くしようとするが、突然叩く音がした。
     5 それは私の訪問を優しく叩くようなものです。
     6「お客様が来ます。」私はささやいた。 「私のドアを叩く。
     7それはすべてです、それはすべてです。 」
     8クイックブラウン
     9回のキツネジャンプ
    10.怠惰な犬。昔々
    11.この退屈な夜中
    12怠惰な犬。昔々
    13. この真夜中は退屈だ。

わかりました、わかりました。最初の6行は消えた。元の行7(「ABCDEFGHIJKLM」)は今行1です。しかし、これは何ですか?ファイルが17行から13行に変更されました。 11(17−6)でなければなりません。最後の2行(「怠惰な犬…真夜中の鈍さ」)が2回登場します。

これは演算子の罠の1つです1<>。出力ファイルを切り捨てないと、起動したファイルよりも小さくないファイルで終わります。具体的には、ここの出力はsed '1,6d' foo462バイト(最初の6行に42バイトが含まれているため504-42)なので、出力ファイルの最初の462バイトを上書きします。これは最後の42fooバイトfooに加えて最初の462バイトです。バイト(504-462) - 最後の2行を上書きしません。最後の2行のコピー2つ(「Lazy dog...真夜中の鈍さ」)はの出力で、sedその後にファイルの元の内容が残ります。

それでは、次は何ですか?

今やるべきことは、ファイルの最後の42バイトを削除することだけです。偶然にもできるポインタをファイルの末尾に移動します。まあ、それは実際にはポインタではありません。整数ファイルサイズ(potAto、potAHto)です。過去20〜30年間、Unixではファイルを必要なサイズに切り捨て、その時点より前のデータをそのまま維持し、その時点以降のデータを削除することができました。

これを実行できる古代のコマンドは次のとおりです。

dd if=/dev/null bs=462 seek=1 of=foo 2> /dev/null

462バイトからコピーします/dev/nullfooはい、ちょっと混乱しています。この機能を実行する新しいコマンドは次のとおりです。

truncate -s 462 foo

これはすべてのシステムに存在しない可能性があります。 POSIXではこれを指定しません。

だからこのすべてを総合してみると、

#!/bin/sh
filename="$1"
bytes_to_remove=$(sed '6q' "$filename" | wc -c)
total_size=$(stat -c '%s' "$filename")
sed '1,6d' "$filename" 1<> "$filename"
new_size=$((total_size - bytes_to_remove))
truncate -s "$new_size" "$filename"

wc -c生成された最初の6行の文字数を数え、sed '6q'それをファイルサイズ全体から減算し、ファイルをそのサイズに切り捨てます。代替コマンドを使用して最初のコマンドを出力できます。中サイズ行または最後の行N~M行の最後の行を次に置き換えることができます。

dd if=/dev/null bs="$new_size" seek=1 of="$filename" 2> /dev/null

指示:

私はこれをファイルでテストしていません

  • CR-LF 行末、または
  • マルチバイト文字、

これは問題になる可能性があります。

答え3

見ている尾の由来、はいいいえ実際にファイル全体を繰り返すようです。最後から始めて、正しい改行の数(終了しない行の過剰分を含む)が表示されるまで、後で読み、その場所を記録してからスキップします。到着その場所を選択し、ファイル(またはパイプまたは入力データ)をダンプします。

関連情報