2013-10-02 3 views
0

Мне нужно удалить определенный номер строки из файла с помощью сценария bash.Удалите определенную строку из файла БЕЗ использования sed или awk

Я получаю номер строки из команды grep с опцией -n.

Я не могу использовать sed по разным причинам, в меньшей степени это то, что он не установлен на всех системах, которые этот сценарий должен запускать, и установка его не является вариантом.

Не может быть и речи об ошибке awk, поскольку при тестировании на разных компьютерах с различными ОС UNIX/Linux (RHEL, SunOS, Solaris, Ubuntu и т. Д.) Он дает (иногда дико) разные результаты по каждому. Итак, нет awk.

Этот файл представляет собой просто текстовый файл с одной записью на строку, поэтому ничего не нужно делать, за исключением удаления строки по номеру.

Если возможно, мне нужно избегать делать что-то вроде извлечения содержимого файла, не считая строки, которую я хочу удалить, а затем перезаписывать исходный файл.

+1

ред или красный может дать вам то, что вы хотите: HTTP: // ип ix.stackexchange.com/questions/58027/removing-consecutive-newlines-with-ed –

+2

можете ли вы включить свой скрипт awk в этот пост. Я очень удивлен вашим утверждением (кроме * возможно * sun 4's (старый) awk (не nawk)). Удачи. – shellter

+0

У меня нет скрипта awk, который использовался первоначально. Эта инструкция исходит от людей, которые зарабатывают больше денег, чем я ... – user2773624

ответ

4

Пробег: . Пример здесь-документ на основе ниже удаляет строку 2 из test.txt

ed -s test.txt <<! 
2d 
w 
! 
+0

мой опыт работы с Sun 'ed' заключается в том, что он не мог читать очень большие файлы без нарушения. Использование режима '' '' '' '' '' '' '' '' '' работает примерно таким же образом и ограничивается только пространством, в котором 'ex' записывает свой временный файл, который настраивается с помощью' tmp =/path/to/tmpdir' (или аналогичного, обратитесь к вашему документу vi). – shellter

+0

«Конечно, задается вопросом, почему OP не просто ищет фактический шаблон, используя * ed * вместо того, чтобы получать номер строки с помощью' grep -n', разглашая это из вывода grep, составляя с ним вход 'ed', и передавая его в редакцию. – kojiro

+1

Когда я запускаю это под 'strace', я вижу, что создается временный файл:' open ("/ tmp/ed.kNTc8I", O_RDWR | O_CREAT | O_EXCL, 0600) = 3'. Не так с решением 'dd', которое я разместил. –

2

Если n это линия вы хотите опустить:

{ 
    head -n $((n-1)) file 
    tail +$((n+1)) file 
} > newfile 
5

Поскольку у вас есть grep, очевидная вещь, чтобы сделать, это:

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

Но похоже, что вы не хотите использовать временные файлы. Я предполагаю, что входной файл большой, и это встроенный sy где память и хранилище не хватает. Я думаю, вам идеально нужно решение, которое редактирует файл на месте. Я думаю, что это может быть возможным с dd, но не понял его еще :(

Update - Я понял, как редактировать файл в месте с дд Также grep, head и cut необходимы.. Если они не доступны, то они, вероятно, можно обойти по большей части:

#!/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 является очень опасным инструмент. Вы можете легко непреднамеренно перезаписать файлы или целые диски. Будь очень осторожен!

+1

Не обижайтесь, но в духе использования правильного инструмента для правильной работы я действительно надеюсь, что OP не пойдет с 'dd' в качестве решения. – kojiro

+0

Без обид. Я согласен с тем, что 'dd' является *** очень опасным *** инструментом, и его использование должно быть очень тщательно рассмотрено. Сказав это, это очень универсальный инструмент, и я думаю, что пока единственный, который можно использовать для правильного ответа на вопрос OPs - то есть удалить строку из файла на месте, без каких-либо временных файлов. –

+0

Проблема с созданием файла temp заключается в том, что этот файл является ОГРОМНЫМ (до 1,9 ГБ на некоторых системах), а файл, где он живет, фактически экспортируется с сервера NFS, который разделяет его на все машины, которые должны использовать файл. Этот сервер NFS также применяет квоты в экспортированном каталоге. Если я попытаюсь использовать его в том же каталоге, что и временный файл, есть действительно хороший шанс, что он превысит квоту на нескольких машинах. Если он сначала попытается перенести его на локальный компьютер, это будет означать много сетевых операций ввода-вывода. Да, я знаю, что настройка/топология безумно сложна. Нет, я не проектировал это ... – user2773624

2

Вы можете сделать это без grep, используя встроенные оболочки posix, которые должны быть на любом * nix.

while read LINE || [ "$LINE" ];do 
    case "$LINE" in 
    *thing_you_are_grepping_for*)continue;; 
    *)echo "$LINE";; 
    esac 
done <infile >outfile 
1

Если вы можете указать, при каких обстоятельствах, на какой платформе (ы) наиболее очевидным Awk сценарий неисправного для вас, возможно, мы можем разработать обходной путь.

awk "NR!=$N" infile >outfile 

Если, конечно, получение $N с grep только кормить его Awk является довольно бас-Ackwards. Это удалит строку, содержащую первое вхождение foo:

awk '/foo/ { if (!p++) next } 1' infile >outfile 
+1

или даже: 'awk '!/Foo/|| p ++' infile> outfile'. Но это не на месте, fwiw. – rici

1

Учитывая dd считаются слишком опасными для этого на месте удаления линии, нам нужен какой-либо другой метод, где мы имеем достаточно детализированный контроль над файловой системой звонки. Мое первоначальное желание - написать что-то в c, но, по возможности, я думаю, что это немного перебор. Вместо этого стоит посмотреть на обычные сценарии (а не на shell-scripting), так как обычно они имеют довольно низкоуровневые файловые API, которые довольно легко сопоставляются с файловыми системами. Я предполагаю, что это можно сделать с помощью python, perl, Tcl или одного из многих других языков сценариев, которые могут быть доступны. Я больше всего знаком с Tcl, так что здесь мы идем:

#!/bin/sh 
# \ 
exec tclsh "$0" "[email protected]" 

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, так что мне пришлось загрузить пакет Tclx для того, чтобы использовать ftruncate команды. В Tcl 8.5 есть chan truncate, который можно использовать вместо этого.

Вы можете передать номер строки, которую хотите удалить, и имя файла для этого скрипта.

Короче говоря, сценарий делает это:

  • открыть файл для чтения
  • читать первый п-1 строки
  • получить смещение начала следующей строки (строка п)
  • чтения линии п
  • открыть файл с новым FD для написания
  • переместить файл местоположения в записи FD на смещение начала строки п
  • продолжить чтение оставшихся строк из чтения FD и записывать их на FD записи, пока все прочитанный FD не читается
  • усечения записи FD

Файла редактируются точно на месте. Никакие временные файлы не используются.

Я уверен, что это может быть переписано в python или perl или ... если необходимо.

Update

Ok, так что в месте удаления линии может быть сделано в почти чистом Баш, используя те же методы для сценария Tcl выше. Но большой оговоркой является то, что вам нужно иметь команду 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)" 

Я хотел бы услышать от более общего (Баш-только?) Способ сделать частичную TRUNCATE в конце и завершить этот ответ. Конечно, усечение можно сделать и с dd, но я думаю, что это уже было исключено для моего более раннего ответа.

И для записи this site перечислены способы выполнения усечения файла на месте на разных языках - в случае, если любой из них может быть использован в вашей среде.

0

на основе answere Digital Trauma, я нашел улучшение, которое просто необходимо Grep и эхо, но не временный файл:

echo $(grep -v PATTERN file.txt) > file.txt 

В зависимости от типа линий файл содержит и является ли ваш шаблон требует более сложного синтаксиса или нет, вы можете принять команду Grep двойные кавычки:

echo "$(grep -v PATTERN file.txt)" > file.txt 

(полезно при удалении от вашего кронтаб)

Смежные вопросы