Как я могу удалить первую строку текстового файла, используя скрипт 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. Не уверен, почему это не лучший ответ.
— Люк Дэвис


17

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

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

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

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

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

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

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


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

1
@db В этом случае, awk FNR-1 *.csvвероятно, быстрее.
— Джинави

10

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

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размер файла. Что вы можете сделать намного быстрее, хотя перезаписать первую строку тем же количеством байтов (возможно, с пробелами или комментарием), что может работать для вас в зависимости от того, что именно вы пытаетесь сделать (что это, кстати?).


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

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}'

Этот синтаксис также будет работать, но только генерировать два выходных файла вместо трех: 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 строках и направлять его в файл, затем удалять старый файл и переименовывать новый файл в старое имя?

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


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

Второе решение подразумевает, что файл не сжимается первой строкой каждый раз. Программа просто обрабатывает его, как если бы оно было сокращено, но каждый раз начинается со следующей строки
— EvilTeach

Я до сих пор не понимаю, какое у вас второе решение.
— Роберт Гэмбл
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.