bash / sedスクリプトを使用してテキストファイルの最初の行を削除するにはどうすればよいですか?


554

bashスクリプトを使用して、巨大なテキストファイルから最初の行を繰り返し削除する必要があります。

現在使用していますsed -i -e "1d" $FILE-削除には1分程度かかります。

これを達成するためのより効率的な方法はありますか?


-iは何を表していますか?
cikatomo

4
@cikatomo:インライン編集を意味します-生成したものでファイルを編集します。
drewrockshard 2013年

4
尾はsedよりもかなり遅いです。テールには13.5秒、セッドには0.85秒が必要です。私のファイルには〜100万行、〜100MBがあります。SSD搭載のMacBook Air 2013。
jcsahnwaldtによると、GoFundMonicaは

回答:


1029

尾を試してください:

tail -n +2 "$FILE"

-n x:最後のx行を印刷します。tail -n 5入力の最後の5行が表示されます。+一種の符号は引数を逆にしtail、最初のx-1行以外のものを出力させます。tail -n +1ファイル全体tail -n +2、最初の行以外すべてを印刷します。

GNUはよりtailもはるかに高速ですsedtailBSDでも利用可能で、-n +2フラグは両方のツールで一貫しています。詳細については、FreeBSDまたはOS Xのマニュアルページを確認してください。

sedただし、BSDのバージョンは、よりも遅くなる可能性があります。彼らはどうやってそれを管理したのだろう。スクリプトを解釈したり、正規表現を適用したりするなど、かなり複雑な操作を行いtailながら、ファイルを1行ずつ読み取る必要がありますsed

注:使いたくなるかもしれません

# THIS WILL GIVE YOU AN EMPTY FILE!
tail -n +2 "$FILE" > "$FILE"

しかし、これはあなたに空のファイルを与えるでしょう。その理由は、リダイレクト(>)がtailシェルによって呼び出される前に発生するためです。

  1. シェルはファイルを切り捨てます $FILE
  2. シェルは新しいプロセスを作成します tail
  3. シェルはtailプロセスのstdoutをにリダイレクトします$FILE
  4. tail 今は空から読む $FILE

ファイル内の最初の行を削除する場合は、次を使用する必要があります。

tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"

&&問題がある場合、ファイルが上書きされないことを確認します。


3
このss64.com/bash/tail.htmlによると、-rオプションでBSD 'tail'を使用する場合、通常のバッファはデフォルトで32kになります。システムのどこかにバッファ設定があるのでしょうか?または-n、32ビットの符号付き番号ですか?
Yzmir Ramirez

41
@Eddie:user869097は、1行が15Mb以上の場合は機能しないと述べています。行が短い限りtail、どのファイルサイズでも機能します。
アーロンディグラ2013

6
これらの議論を説明できますか?
Dreampuf 2013年

17
@Dreampuf-manページから:-n N means output the last N lines, instead of the last 10; or use +N to output lines starting with the Nth
Will Sheppard

11
私は@JonaChristopherSahnwaldtに同意するつもりでした-テールはsedバリアントよりもはるかに遅く、桁違いです。500,000K行(1行あたり50文字以下)のファイルでテストしています。しかし、それから私はFreeBSDバージョンのテール(デフォルトでOS Xに付属しています)を使用していることに気付きました。私がGNUテールに切り替えたとき、テールコールはsedコール(およびGNU sedコールも)よりも10倍高速でした。GNUを使用している場合は、AaronDigullaが正しいです。
Dan Nguyen

179

-iを使用すると、「>」演算子を使用せずにファイルを更新できます。次のコマンドは、ファイルから最初の行を削除し、ファイルに保存します。

sed -i '1d' filename

1
エラーが表示されます:unterminated transform source string
ダニエル神戸

10
これは常に機能し、本当にトップの答えになるはずです!
xtheking 2017年

4
念のため、Macでインプレース編集でsedを使用する場合は、サフィックスを指定する必要があります。したがって、-i.bakを使用して上記を実行します
mjp

3
ただのメモ-いくつかの行を削除するために使用sed -i '1,2d' filename
ゴッドファーザー

4
このバージョンは、よりも可読性が高く、より普遍的ですtail -n +2。なぜそれがトップアンサーではないのか分かりません。
ルークデイビス


17

いいえ、それはあなたが得ようとしているのと同じくらい効率的です。あなたは少し速く(起動時間と処理引数が少ない)仕事をすることができるCプログラムを書くことができますが、おそらくファイルが大きくなるにつれてsedと同じ速度になる傾向があります(そして、私はそれが1分かかる場合は大きいと思います) )。

しかし、あなたの質問は、それが解決策を前提としているという点で、他の多くの問題と同じ問題に悩まされています。方法ではなく、をしようとしているのかを詳しく教えていただければ、より適切なオプションを提案できる可能性があります。

たとえば、これが他のプログラムBが処理するファイルAである場合、1つの解決策は、最初の行を削除せずに、プログラムBを変更して別の方法で処理することです。

すべてのプログラムがこのファイルAに追加し、プログラムBが現在、最初の行を読み取って処理してから削除するとします。

プログラムBを再設計して、最初の行を削除しようとせずに、ファイルAへの永続的な(おそらくファイルベースの)オフセットを維持して、次に実行したときに、そのオフセットにシークすることができるようにします。そこの行、そしてオフセットを更新します。

次に、静かな時間(真夜中?)に、ファイルAの特別な処理を行って、現在処理されているすべての行を削除し、オフセットを0に戻します。

プログラムがファイルを開いて書き換えるよりも、ファイルを開いてシークする方が確かに高速です。この説明では、もちろんプログラムBを制御できることを前提としています。それが事実であるかどうかはわかりませんが、さらに情報を提供すると、他の解決策が考えられる場合があります。


OPは私がこの質問を見つけた理由を達成しようとしていると思います。500k行のCSVファイルが10個あります。すべてのファイルには、最初の行と同じヘッダー行があります。私は猫です:これらのファイルを1つのファイルにしてからDBにインポートし、DBに最初の行から列名を作成させます。明らかに、ファイル2-10でその行が繰り返されることは望ましくありません。
db

1
@dbその場合、awk FNR-1 *.csvおそらくより高速です。
jinawee

10

次のように、perlのフラグを使用するだけでファイル編集できます-i

perl -ni -e 'print unless $. == 1' filename.txt

これにより、最初の行が表示されなくなります。Perlはファイル全体を読み取ってコピーする必要がありますが、出力は元のファイルの名前で保存されるようになっています。


10

あなたはこれで簡単にこれを行うことができます:

cat filename | sed 1d > filename_without_first_line

コマンドライン; または、ファイルの最初の行を永久に削除するには、sedのインプレースモードを-iフラグと共に使用します。

sed -i 1d <filename>

9

パックスが言ったように、あなたはおそらくこれより速くなることはないでしょう。その理由は、ファイルの先頭からの切り捨てをサポートするファイルシステムがほとんどないため、これは、ファイルのサイズがnであるO()操作nになるためです。はるかに速くできるのは、最初の行を同じバイト数で(おそらくスペースまたはコメントで)上書きすることです。これは、何をしようとしているのかによって異なります(ところで何ですか)。


「サポートは切り捨てることを... ...ほとんどのファイルシステム」:それは面白いです。そのようなファイルシステムの名前を示す括弧付きのメモを含めることを検討してください。
agc

1
@agc:現在は関係ありませんが、70年代の最初の仕事は小さなスタートアップのQuadexでした(今はなくなり、現在その名前を使用している2つの会社とは無関係です)。彼らはファイルの最初または最後のいずれかで追加または削除を許可するファイルシステムを持っていました。これは主に、ウィンドウにウィンドウの上とウィンドウの下に置くことによって3KB未満で編集を実装するために使用されました。独自の名前はなく、QuadexマルチユーザーオペレーティングシステムのQMOSの一部にすぎません。(「マルチ」は通常、LSI-11 / 02で2-3で、RAMは64KB未満で、通常、RX01タイプの8インチフロッピーディスクはそれぞれ250KBです。):-)
dave_thompson_085

9

spongeutilのは、一時ファイルをジャグリングの必要性を回避します:

tail -n +2 "$FILE" | sponge "$FILE"

sponge確かに、受け入れられているソリューションよりもはるかにクリーンで堅牢です(tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"
Jealie

1
「スポンジ」には「moreutils」パッケージのインストールが必要であることを明確にする必要があります。
FedFranzoni

これは、(Debian Dockerイメージ上の)システムファイルを変更するために機能した唯一のソリューションです。他の解決策は、ファイルを書き込もうとしたときに「デバイスまたはリソースがビジー状態です」エラーが原因で失敗しました。
FedFranzoni

しかしsponge、ファイル全体をメモリにバッファリングしますか?数百GBの場合は機能しません。
OrangeDog

@ OrangeDog、/ tmpファイルを中間ステップとして使用し、後で元のファイルを置き換えるために使用されるため、ファイルシステムがそれを格納できる限り、それspongeを吸収します。
agc

8

あなたの場所にファイルを変更したい場合は、常に元使えるed代わりにのSを後継者をtreaming sed

ed "$FILE" <<<$'1d\nwq\n'

edでもフルスクリーン端末、はるかに少ないのグラフィックワークステーションがあった前のコマンドは、オリジナルのUNIXのテキストエディタでした。exエディタは、最高のタイピングが大腸プロンプトででたときに、使用しているものとして知られているvi、あるの傾向が見られたバージョンedと同じコマンドの作業の多くは、。一方でed対話的に使用されることを意味し、それはまた、このソリューションが何をしている、それにコマンド文字列を送信することにより、バッチモードで使用することができます。

シーケンスは、<<<$'1d\nwq\n'ここでは、文字列(のためのBashのサポートを利用しています<<<)とPOSIXの引用符($'... ')フィード入力にするed:二行からなるコマンド1dDの eletesがライン1に続いて、wqこれ、ワットに出儀式ファイルバックディスクとは、qは編集セッションをuits。


これはエレガントです。+1
アーミン

ただし、ファイル全体をメモリに読み込む必要があります。これは、数百GBの場合は機能しません。
OrangeDog

5

最初の行以外の行が表示されます。

cat textfile.txt | tail -n +2

4
-「tail -n +2 textfile.txt」を実行する必要があります
niglesias

5
@niglesiaisこのソリューションはファイルだけでなくパイプされたコンテンツでも問題ないことを明確にしているため、「猫の無用な使用」には同意しません。
Titou

5

vimを使用してこれを行うことができます:

vim -u NONE +'1d' +'wq!' /tmp/test.txt

処理時にvimはファイル全体を読み取らないため、これはより高速になります。


+wq!シェルがbashの場合は引用する必要があります。多分それ!が単語の最初ではないからではないでしょう、しかし物事を引用する習慣を身につけることは多分周りに良いでしょう。(そして、不必要に引用しないことで超効率を追求するのであれば、1dどちらも引用する必要はありません。)
Mark Reed

vim ファイル全体を読み取る必要あります。実際、このQで尋ねられたように、ファイルがメモリよりも大きい場合、vimはファイル全体(またはそのほとんど)を読み取り、一時ファイルに書き込みます。編集後、ファイルをすべて(永続ファイルに)書き戻します。私はあなたがそれはおそらく仕事ができると思うのか分からないことなく、これ。
dave_thompson_085

4

csplitはどうでしょうか?

man csplit
csplit -k file 1 '{1}'

この構文も機能しますが、生成される出力ファイルは3つではなく2つだけですcsplit file /^.*$/1。以上簡単に:csplit file //1。またはさらに簡単に:csplit file 2
Marco Roy

1

削除を高速化できないように思えるので、次のようにファイルをバッチで処理するのが良い方法だと思います。

While file1 not empty
  file2 = head -n1000 file1
  process file2
  sed -i -e "1000d" file1
end

これの欠点は、プログラムが途中で終了した場合(または、そこにいくつかの不良SQLがある場合-「プロセス」の部分が停止またはロックアップする原因)、スキップされるか、2回処理される行が存在することです。 。

(file1にはSQLコードの行が含まれています)


最初の行には何が含まれていますか?私の投稿で提案したように、SQLコメントで上書きすることはできますか?
ロバートギャンブル

0

失敗した後の回復が目的の場合は、これまでに行ったことのあるファイルを作成するだけです。

if [[ -f $tmpf ]] ; then
    rm -f $tmpf
fi
cat $srcf |
    while read line ; do
        # process line
        echo "$line" >> $tmpf
    done

0

この1つのライナーは次のことを行います。

echo "$(tail -n +2 "$FILE")" > "$FILE"

ので、それは、働くtail前に実行されていないecho一時ファイルは不要、したがって、その後、ファイルのロックが解除されます。


-1

N-1行でテールを使用し、それをファイルに送り、その後古いファイルを削除し、新しいファイルの名前を古い名前に変更すると、うまくいきますか?

プログラムでこれを行っている場合は、各行を読み取った後、ファイルを読み取り、ファイルオフセットを覚えているので、その位置に戻って、1行少ないファイルを読み取ることができます。


最初のソリューションは、ブレントが現在行っているものと本質的に同じです。私はあなたのプログラム的なアプローチを理解していません、最初の行だけを削除する必要があります。最初の行を読み取って破棄し、残りを別のファイルにコピーします。これもsedおよびtailアプローチと同じです。
ロバートギャンブル、

2番目の解決策は、ファイルが毎回最初の行で縮小されないことを意味します。プログラムは単に縮小されたかのように処理するだけですが、毎回次の行から開始します
EvilTeach

私はまだあなたの2番目の解決策が何であるか理解していません。
Robert Gamble
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.