1

bash スクリプトを使用して、ファイルから特定の行番号を削除する必要があります。

-n オプションを指定して grep コマンドから行番号を取得します。

さまざまな理由で sed を使用できません。少なくとも、このスクリプトを実行する必要があるすべてのシステムにインストールされていないため、インストールすることはできません。

awk は問題外です。テストでは、異なる UNIX/Linux OS (RHEL、SunOS、Solaris、Ubuntu など) を搭載した異なるマシンで、それぞれに (時には非常に) 異なる結果が得られるためです。だから、大丈夫です。

問題のファイルは、行ごとに 1 つのレコードを含む単純なテキスト ファイルであるため、番号で行を削除する以外は何もする必要はありません。

可能な限り、削除したい行を含めずにファイルの内容を抽出して元のファイルを上書きするようなことは避ける必要があります。

4

7 に答える 7

7

あなたが持っているのでgrep、やるべきことは明らかです:

$ grep -v "line to remove" file.txt > /tmp/tmp
$ mv /tmp/tmp file.txt
$

しかし、一時ファイルを使用したくないようです-入力ファイルが大きく、これはメモリとストレージが不足している組み込みシステムであると思います。理想的には、ファイルをその場で編集するソリューションが必要だと思います。これは可能かもしれないと思いますが、ddまだ理解していません:(

更新- dd を使用してその場でファイルを編集する方法を見つけました。またgrepheadcutが必要です。これらが利用できない場合は、ほとんどの場合、おそらく回避できます。

#!/bin/bash

# get the line number to remove
rline=$(grep -n "$1" "$2" | head -n1 | cut -d: -f1)
# number of bytes before the line to be removed
hbytes=$(head -n$((rline-1)) "$2" | wc -c)
# number of bytes to remove
rbytes=$(grep "$1" "$2" | wc -c)
# original file size
fsize=$(cat "$2" | wc -c)
# dd will start reading the file after the line to be removed
ddskip=$((hbytes + rbytes))
# dd will start writing at the beginning of the line to be removed
ddseek=$hbytes
# dd will move this many bytes
ddcount=$((fsize - hbytes - rbytes))
# the expected new file size
newsize=$((fsize - rbytes))
# move the bytes with dd.  strace confirms the file is edited in place
dd bs=1 if="$2" skip=$ddskip seek=$ddseek conv=notrunc count=$ddcount of="$2"
# truncate the remainder bytes of the end of the file
dd bs=1 if="$2" skip=$newsize seek=$newsize count=0 of="$2"

次のように実行します。

$ cat > file.txt
line 1
line two
line 3
$ ./grepremove "tw" file.txt
7+0 records in
7+0 records out
0+0 records in
0+0 records out
$ cat file.txt
line 1
line 3
$ 

非常に危険なツールddであると言えます。ファイルやディスク全体を意図せず簡単に上書きしてしまう可能性があります。十分気をつける!

于 2013-10-02T03:45:15.097 に答える
2

* nixにあるはずのposixシェルビルトインを使用して、grepなしで実行できます。

while read LINE || [ "$LINE" ];do
  case "$LINE" in
    *thing_you_are_grepping_for*)continue;;
    *)echo "$LINE";;
  esac
done <infile >outfile
于 2013-10-02T04:40:48.560 に答える
2

このインプレース行の削除は危険すぎるddと考えられるため、ファイル システム呼び出しをかなりきめ細かく制御できる別の方法が必要です。私の最初の衝動は、c で何かを書きたいということですが、可能ではありますが、それは少しやり過ぎだと思います。代わりに、一般的なスクリプト (シェル スクリプトではない) 言語に目を向ける価値があります。これらの言語には通常、かなり単純な方法でファイル syscall にマップされるかなり低レベルのファイル API があるためです。これは、python、perl、Tcl、または利用可能な他の多くのスクリプト言語のいずれかを使用して実行できると思います。私はTclに最も精通しているので、ここに行きます:

#!/bin/sh
# \
exec tclsh "$0" "$@"

package require Tclx

set removeline [lindex $argv 0]
set filename [lindex $argv 1]

set infile [open $filename RDONLY]
for {set lineNumber 1} {$lineNumber < $removeline} {incr lineNumber} {
    if {[eof $infile]} {
        close $infile
        puts "EOF at line $lineNumber"
        exit
    }
    gets $infile line
}
set bytecount [tell $infile]
gets $infile rmline

set outfile [open $filename RDWR]
seek $outfile $bytecount start

while {[gets $infile line] >= 0} {
    puts $outfile $line
}

ftruncate -fileid $outfile [tell $outfile]
close $infile
close $outfile

私の特定のボックスには Tcl 8.4 があることに注意してください。したがって、ftruncate コマンドを使用するには Tclx パッケージをロードする必要がありました。Tcl 8.5 では、chan truncate代わりに使用できるものがあります。

削除する行番号とファイル名をこのスクリプトに渡すことができます。

つまり、スクリプトは次のことを行います。

  • ファイルを読み取り用に開く
  • 最初の n-1 行を読む
  • 次の行 (行 n) の開始位置のオフセットを取得します
  • n 行目を読む
  • 書き込み用に新しい FD でファイルを開きます
  • 書き込み用 FD のファイルの場所を n 行目の先頭のオフセットに移動します
  • 読み取りFDから残りの行を読み取り続け、読み取りFD全体が読み取られるまで書き込みFDに書き込みます
  • 書き込みFDを切り捨てる

ファイルはその場で正確に編集されます。一時ファイルは使用されません。

これは、必要に応じて python や perl などで書き直すことができると確信しています。

アップデート

上記の Tcl スクリプトと同様の手法を使用して、ほぼ純粋な bash でインプレース行削除を実行できます。truncateただし、大きな注意点は、コマンドを使用できるようにする必要があるということです。Ubuntu 12.04 VM にはありますが、古い Redhat ベースのボックスにはありません。スクリプトは次のとおりです。

#!/bin/bash

n=$1
filename=$2
exec 3<> $filename
exec 4<> $filename
linecount=1
bytecount=0
while IFS="" read -r line <&3 ; do
    if [[ $linecount == $n ]]; then
        echo "omitting line $linecount: $line"
    else
        echo "$line" >&4
        ((bytecount += ${#line} + 1))
    fi
    ((linecount++))
done
exec 3>&-
exec 4>&-

truncate -s $bytecount $filename
#### or if you can tolerate dd, just to do the truncate:
# dd of="$filename" bs=1 seek=$bytecount count=0
#### or if you have python
# python -c "open(\"$filename\", \"ab\").truncate($bytecount)"

最後に部分的な切り捨てを行い、この回答を完成させるためのより一般的な(bashのみの?)方法を知りたいです。もちろん、切り捨ても同様に実行できますがdd、以前の回答ではすでに除外されていたと思います。

記録のために、このサイトには、さまざまな言語でインプレース ファイルの切り捨てを行う方法がリストされています。これらのいずれかが環境で使用される場合に備えて。

于 2013-10-02T22:32:42.110 に答える