Как я могу удалить первую строку текстового файла, используя скрипт bash / sed?

555

Мне нужно многократно удалять первую строку из огромного текстового файла, используя скрипт bash.

Прямо сейчас я использую sed -i -e "1d" $FILE- но удаление занимает около минуты.

Есть ли более эффективный способ сделать это?

казарка
источник
что означает -i?
cikatomo
4
@cikatomo: он обозначает встроенное редактирование - он редактирует файл с тем, что вы генерируете.
drewrockshard
4
Хвост НАМНОГО МЕДЛЕН, чем сед. хвосту нужно 13,5 с, седу - 0,85 с. Мой файл имеет ~ 1 млн строк, ~ 100 МБ. MacBook Air 2013 с SSD.
jcsahnwaldt говорит GoFundMonica

Ответы:

1031

Попробуй хвост :

tail -n +2 "$FILE"

-n x: Просто напечатайте последние xстроки. tail -n 5даст вам последние 5 строк ввода. +Знак рода инвертирует аргумент и сделать tailпечать ничего , кроме первых x-1строк. tail -n +1будет печатать весь файл, tail -n +2все, кроме первой строки и т. д.

GNU tailнамного быстрее чем sed. tailтакже доступен на BSD, и -n +2флаг одинаков для обоих инструментов. Проверьте справочные страницы FreeBSD или OS X для получения дополнительной информации.

Версия BSD может быть намного медленнее, чем sed, однако. Интересно, как им это удалось; tailследует просто читать файл построчно, в то время как sedвыполняет довольно сложные операции, включая интерпретацию скрипта, применение регулярных выражений и тому подобное.

Примечание: вы можете испытать желание использовать

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

но это даст вам пустой файл . Причина в том, что redirection ( >) происходит до того, tailкак вызывается оболочкой:

  1. Файл усеченных оболочек $FILE
  2. Shell создает новый процесс для tail
  3. Shell перенаправляет стандартный вывод tailпроцесса на$FILE
  4. tail читает из теперь пусто $FILE

Если вы хотите удалить первую строку внутри файла, вы должны использовать:

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

&&Будет убедиться , что файл не будет перезаписан , когда есть проблема.

Аарон Дигулла
источник
3
В соответствии с этим ss64.com/bash/tail.html типичный буфер по умолчанию равен 32 КБ при использовании BSD 'tail' с -rопцией. Может быть, где-то в системе есть настройка буфера? Или -nэто 32-битный номер со знаком?
Измир Рамирес
41
@Eddie: user869097 сказал, что это не работает, если одна строка составляет 15 МБ или больше. Пока строки будут короче, tailбудут работать файлы любого размера.
Аарон Дигулла
6
Вы могли бы объяснить эти аргументы?
Dreampuf
17
@Dreampuf - со страницы руководства:-n N means output the last N lines, instead of the last 10; or use +N to output lines starting with the Nth
Уилл Шеппард
11
Я собирался согласиться с @JonaChristopherSahnwaldt - tail намного, намного медленнее, чем вариант sed, на порядок. Я тестирую его на файле с 500 000K строк (не более 50 символов в строке). Однако затем я понял, что использую версию tail для FreeBSD (которая по умолчанию поставляется с OS X). Когда я переключился на GNU tail, хвостовой вызов был в 10 раз быстрее, чем вызов sed (и вызов GNU sed тоже). AaronDigulla является правильным здесь, если вы используете GNU.
Дэн Нгуен
179

Вы можете использовать -i для обновления файла без использования оператора «>». Следующая команда удалит первую строку из файла и сохранит ее в файл.

sed -i '1d' filename
Амит
источник
1
Я получаю ошибку:unterminated transform source string
Даниэль Кобе
10
это работает каждый раз и действительно должно быть лучшим ответом!
xtheking
4
Напомним, что Mac требует предоставления суффикса при использовании sed с правками на месте. Так что запустите выше с -i.bak
mjp
3
Просто примечание - чтобы удалить несколько строк использоватьsed -i '1,2d' filename
Крестный отец
4
Эта версия действительно намного более читабельна и универсальна, чем tail -n +2. Не уверен, почему это не лучший ответ.
Люк Дэвис
74

Для тех, кто работает в SunOS, отличной от GNU, поможет следующий код:

sed '1d' test.dat > tmp.dat 
Насри Наджиб
источник
18
Интересная демография
капитан
17

Нет, это примерно так же эффективно, как вы собираетесь получить. Вы могли бы написать программу на C, которая могла бы выполнять работу немного быстрее (меньше времени запуска и обработки аргументов), но она, вероятно, будет стремиться к той же скорости, что и sed, когда файлы становятся большими (и я предполагаю, что они велики, если это займет минуту ).

Но ваш вопрос страдает от той же проблемы, что и многие другие, в том смысле, что он предполагает решение. Если бы вы подробно рассказали нам о том, что вы пытаетесь сделать, а не о том , как , мы можем предложить лучший вариант.

Например, если это файл A, который обрабатывает какая-то другая программа B, одним из решений было бы не убрать первую строку, а изменить программу B для ее обработки по-другому.

Допустим, все ваши программы добавляют к этому файлу A, и программа B в настоящее время читает и обрабатывает первую строку перед ее удалением.

Вы могли бы перепроектировать программу B так, чтобы она не пыталась удалить первую строку, но сохранила постоянное (вероятно, основанное на файле) смещение в файле A, чтобы при следующем запуске она могла искать это смещение, обрабатывая линия там, и обновить смещение.

Затем в тихое время (полночь?) Он может выполнить специальную обработку файла A, чтобы удалить все обрабатываемые в данный момент строки и установить смещение обратно на 0.

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

paxdiablo
источник
Я думаю, что ОП пытается добиться того, что заставило меня найти этот вопрос. У меня есть 10 файлов CSV с 500k строк в каждом. Каждый файл имеет ту же строку заголовка, что и первая строка. Я кошка: эти файлы в один файл, а затем импортировать их в БД, позволяя БД создавать имена столбцов из первой строки. Очевидно, я не хочу, чтобы эта строка повторялась в файле 2-10.
дб
1
@db В этом случае, awk FNR-1 *.csvвероятно, быстрее.
Джинави
10

Вы можете редактировать файлы на месте: просто используйте -iфлаг Perl , например:

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

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

Alexis
источник
10

Вы можете легко сделать это с:

cat filename | sed 1d > filename_without_first_line

в командной строке; или чтобы навсегда удалить первую строку файла, используйте режим sed с -iфлагом:

sed -i 1d <filename>
Инго Бааб
источник
9

Как сказал Пакс, вы, вероятно, не получите быстрее, чем это. Причина в том, что почти нет файловых систем, которые поддерживают усечение с начала файла, поэтому это будет nоперация O ( ), где nразмер файла. Что вы можете сделать намного быстрее, хотя перезаписать первую строку тем же количеством байтов (возможно, с пробелами или комментарием), что может работать для вас в зависимости от того, что именно вы пытаетесь сделать (что это, кстати?).

Роберт Гэмбл
источник
Re "... почти нет файловых систем, поддерживающих усечение ..." : это интересно; пожалуйста, рассмотрите возможность включения такой файловой системы в скобки.
АРУ
1
@agc: сейчас не имеет значения, но моя первая работа в 70-х годах была с Quadex, небольшим стартапом (сейчас ушедшим и не связанным с двумя компаниями, использующими это имя). У них была файловая система, которая позволяла добавлять или удалять файлы в начале или в конце, используемые в основном для осуществления редактирования размером менее 3 КБ, помещая в файлы выше и ниже окна. У него не было собственного названия, оно было частью QMOS, многопользовательской операционной системы Quadex. («Multi» обычно было 2-3 на LSI-11/02 с объемом оперативной памяти менее 64 КБ и обычно по несколько дискет RX01-типа 8 "каждая по 250 КБ.) :-)
dave_thompson_085
9

spongeUtil позволяет избежать необходимости жонглировать временный файл:

tail -n +2 "$FILE" | sponge "$FILE"
АРУ
источник
spongeдействительно намного чище и надежнее, чем принятое решение ( tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE")
Jealie
1
Следует четко указать, что для 'sponge' требуется установить пакет 'moreutils'.
ФедФранцони
Это единственное решение, которое помогло мне изменить системный файл (на образе докера Debian). Другие решения не удалось из-за ошибки «Устройство или ресурс занят» при попытке записи файла.
ФедФранцони
Но spongeбуферизует ли весь файл в памяти? Это не сработает, если это сотни ГБ.
OrangeDog
@OrangeDog, пока файловая система может хранить его, spongeбудет впитывать его, так как он использует файл / tmp в качестве промежуточного шага, который затем используется для замены исходного впоследствии.
АРУ
8

Если вы хотите изменить файл в месте, вы всегда можете использовать оригинал edвместо его s treaming преемника sed:

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

Эта edкоманда была оригинальным текстовым редактором UNIX, еще до появления полноэкранных терминалов, а тем более графических рабочих станций. exРедактор, известный как то , что вы используете , когда набрав в командной строке в Колоне vi, является бывшей , как правило , версия ed, так что многие из той же работы команд. Хотя edон предназначен для интерактивного использования, его также можно использовать в пакетном режиме, посылая ему строку команд, что и делает это решение.

Последовательность <<<$'1d\nwq\n'пользуется поддержкой Bash для здесь-строк ( <<<) и POSIX кавычки ( $'... ') для ввода подачи в edкоманду , состоящая из двух линий: 1d, что г eletes выравнивает 1 , а затем wq, какой ж обряды файл обратно в диск , а затем д UITS сеанс редактирования.

Марк Рид
источник
это элегантно +1
Армин
Но вы должны прочитать весь файл в память, которая не будет работать, если это сотни ГБ.
OrangeDog
5

должны показывать строки кроме первой строки:

cat textfile.txt | tail -n +2
Serup
источник
4
- ты должен сделать "tail -n +2 textfile.txt"
niglesias
5
@niglesiais Я не согласен с «бесполезным использованием кошки», так как оно ясно дает понять, что это решение подходит для содержимого по конвейеру, а не только для файлов.
Titou
5

Может использовать vim для этого:

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

Это должно быть быстрее, так как vim не будет читать весь файл при обработке.

Хунбо Лю
источник
Может потребоваться указать, +wq!если ваша оболочка bash. Вероятно, не потому, что !это не в начале слова, но привычка цитировать вещи, вероятно, хорошо во всем. (И если вы стремитесь к суперэффективности, не цитируя без необходимости, вам не нужны кавычки вокруг 1d.)
Марк Рид
ВИМ действительно нужно читать весь файл. На самом деле, если файл больше памяти, как было задано в этом вопросе, vim читает весь файл и записывает его (или большую его часть) во временный файл, а после редактирования записывает все обратно (в постоянный файл). Я не знаю, как вы думаете, это могло бы работать без этого.
dave_thompson_085
4

Как насчет использования csplit?

man csplit
csplit -k file 1 '{1}'
Shahbaz
источник
Этот синтаксис также будет работать, но только генерировать два выходных файла вместо трех: csplit file /^.*$/1. Или еще проще: csplit file //1. Или еще проще: csplit file 2.
Марко Рой
1

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

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

Недостаток этого заключается в том, что если программа будет убита в середине (или если там будет какой-то плохой sql - что приведет к смерти или блокировке части процесса), будут строки, которые либо пропускаются, либо обрабатываются дважды ,

(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

Этот лайнер сделает:

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

Это работает, поскольку tailвыполняется до, echoа затем файл разблокируется, следовательно, нет необходимости во временном файле.

Егоров
источник
-1

Будет ли работать хвост на N-1 строках и направлять его в файл, затем удалять старый файл и переименовывать новый файл в старое имя?

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

EvilTeach
источник
Первое решение по сути идентично тому, что сейчас делает Brent. Я не понимаю ваш программный подход, нужно удалить только первую строку, вы просто прочитали бы и отбросили первую строку и скопировали остальную часть в другой файл, который снова совпадает с подходами sed и tail.
Роберт Гэмбл
Второе решение подразумевает, что файл не сжимается первой строкой каждый раз. Программа просто обрабатывает его, как если бы оно было сокращено, но каждый раз начинается со следующей строки
EvilTeach
Я до сих пор не понимаю, какое у вас второе решение.
Роберт Гэмбл