Як я можу видалити перший рядок текстового файлу за допомогою сценарію bash / sed?


554

Мені потрібно неодноразово видаляти перший рядок з величезного текстового файлу за допомогою скрипту bash.

Зараз я використовую sed -i -e "1d" $FILE- але для видалення потрібно близько хвилини.

Чи є більш ефективний спосіб досягти цього?


що означає -і означає?
cikatomo

4
@cikatomo: це розшифровка вбудованого редагування - він редагує файл із усім, що ви створюєте.
drewrockshard

4
хвіст набагато повільніше, ніж sed. хвосту потрібно 13,5s, sed потрібно 0,85s. Мій файл має ~ 1М рядків, ~ 100 Мб. MacBook Air 2013 з SSD.
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набагато швидше, ніж sed. tailтакож доступний на BSD, і -n +2прапор є послідовним для обох інструментів. Перевірте докладні сторінки FreeBSD або OS X для отримання додаткової інформації.

Однак версія BSD може бути набагато повільнішою, ніж sedвсе-таки. Цікаво, як їм це вдалося; tailслід просто читати файл за рядком, в той час як sedвиконує досить складні операції, пов’язані з інтерпретацією сценарію, застосуванням регулярних виразів тощо.

Примітка: Ви можете спокуситись використовувати

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

але це дасть вам порожній файл . Причина полягає в тому, що оболонка викликає перенаправлення ( >) раніше tail:

  1. Файл обрізки оболонки $FILE
  2. Shell створює новий процес для tail
  3. Shell перенаправляє stdout tailпроцесу на$FILE
  4. tail читає з тепер порожнього $FILE

Якщо ви хочете видалити перший рядок із файлу, слід використовувати:

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

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


3
Відповідно до цього ss64.com/bash/tail.html типовий буфер за замовчуванням до 32k при використанні BSD 'хвоста' з -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 - хвіст набагато, набагато повільніше, ніж варіант sed, на порядок. Я тестую його у файлі розміром 500 000 К (не більше 50 символів на рядок). Однак я зрозумів, що використовую хвіст FreeBSD-версії (яка за замовчуванням поставляється з OS X). Коли я перейшов на хвіст GNU, хвостовий виклик був у 10 разів швидшим, ніж виклик sed (і дзвінок sed GNU sed) теж. Тут використовується AaronDigulla, якщо ви використовуєте GNU.
Dan Nguyen

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, щоб наступного разу, коли він запускається, він міг прагнути до цього зміщення, обробляти рядок там, і оновити зміщення.

Потім, у тихий час (опівночі?), Вона могла б виконати спеціальну обробку файлу А, щоб видалити всі оброблювані нині рядки та встановити зміщення назад на 0.

Звичайно, програма буде швидше відкриватись і шукати файл, а не відкривати та переписувати. Ця дискусія передбачає, що ви, звичайно, маєте контроль над програмою B. Я не знаю, чи так це, але якщо ви надасте додаткову інформацію, можуть бути інші можливі рішення.


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

1
@db У цьому випадку, awk FNR-1 *.csvмабуть, швидше.
jinawee

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. ("Мульти" зазвичай було 2-3 на LSI-11/02 з оперативною пам'яттю менше 64 КБ та зазвичай декількома дискетами типу "RX01 8" на кожні 250
КБ

9

spongeUtil дозволяє уникнути необхідності жонглювати тимчасовий файл:

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

spongeнасправді набагато чистіші та надійніші, ніж прийняте рішення ( tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE")
Желлі

1
Слід уточнити, що "губка" вимагає встановлення пакета "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, який ж обряди файл назад в диск, а потім q використовує сеанс редагування.


це елегантно. +1
Армін

Але ви повинні прочитати весь файл в пам'яті, який не спрацює, якщо це сотні ГБ.
OrangeDog

5

повинні показувати рядки, крім першого рядка:

cat textfile.txt | tail -n +2

4
- ви повинні робити "хвіст -n +2
textfile.txt

5
@niglesiais Я не погоджуюся з "марним використанням кота", оскільки це дає зрозуміти, що це рішення нормально для конвеєрного вмісту, а не лише для файлів.
Титу

5

Для цього можна використовувати vim:

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

Це має бути швидше, оскільки vim не буде читати весь файл під час обробки.


Можливо, потрібно процитувати, +wq!якщо ваша оболонка - це башти. Напевно, це не так, оскільки !це не на початку слова, але звичка цитувати речі, мабуть, добре все навколо. (І якщо ви збираєтеся отримати суперефективність, не цитуючи зайвих зусиль, вам також не потрібні цитати 1d.)
Марк Рід

ВІМ дійсно потрібно читати весь файл. Насправді, якщо файл більший за об'єм пам'яті, про що запитували в цьому Q, 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 - спричиняючи загибель або замикання частини "процес"), будуть рядки, які або пропускаються, або обробляються двічі .

(файл1 містить рядки коду 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 і направити його у файл, після чого видалити старий файл та перейменувати новий файл на старе ім'я?

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


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

Друге рішення має на увазі те, що файл не стискається першим рядком кожного разу. Програма просто обробляє її, як ніби вона скоротилася, але починаючи з наступного рядка кожен раз
EvilTeach

Я досі не розумію, що ти є другим рішенням.
Роберт Гембл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.