Як видалити останній рядок усіх файлів з каталогу?


17

У мене в тексті багато текстових файлів, і я хочу видалити останній рядок кожного файлу в каталозі.

Як я можу це зробити?


6
Що ви пробували? unix.stackexchange.com/help/how-to-ask : "Спільне використання ваших досліджень допомагає всім. Розкажіть нам, що ви знайшли і чому це не відповідає вашим потребам. Це свідчить про те, що ви знайшли час, щоб спробувати допомогти собі , це рятує нас від повторення очевидних відповідей, і, перш за все, допомагає отримати більш конкретну та релевантну відповідь! "
Патрік

Чому думка про те, що хтось випадково застосував це до / і т.
Д.,

Відповіді:


4

Якщо у вас є доступ vim, ви можете використовувати:

for file in ./*
do
  if [ -f "${file}" ]
  then
    vim -c '$d' -c "wq" "${file}"
  fi
done

16

Ви можете використовувати цей хороший oneliner, якщо у вас є GNU sed.

 sed -i '$ d' ./*

Він видалить останній рядок кожного не прихованого файлу з поточного каталогу. Перемикач -iдля GNU sedозначає працювати на місці та '$ d'командує sedдля видалення останнього рядка ( $означає останнє та dзначення видалення).


3
Це призведе до помилок (і нічого не робити), якщо в папці є щось, крім звичайного файлу, наприклад, інша папка ...
Najib Idrissi

1
@StefanR Ви використовуєте, -iщо є GNUism, тому це суперечка, але я втратив би свою стару бороду, якби я не зміг зазначити, що деякі старіші версії sedне дозволяють вам розміщувати пробіли між $і d(або, взагалі між шаблоном і командою).
zwol

1
@zwol Як я писав, це призведе до помилки , а не до попередження, і sed відмовиться, як тільки дістанеться до цього файлу (принаймні, з версією sed у мене). Наступні файли не обробляються. Викинути повідомлення про помилки було б жахливою ідеєю, оскільки ви навіть не знали, що це сталося! З zsh ви можете використовувати *(.)для глобальних файлів для звичайних файлів, я не знаю про інші оболонки.
Наджиб Ідріссі

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

@don_crissti У мене також є GNU sed v4.3 ... Я не знаю, що тобі сказати, я просто перевірив ще раз. gist.github.com/nidrissi/66fad6be334234f5dbb41c539d84d61e
Наджиб Ідрісса

11

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

find "$dir" -type f -exec sed -i '$d' '{}' '+'
  • find "$dir" -type f: знайдіть файли в каталозі $dir
    • -type f які є звичайними файлами;
    • -exec виконати команду на кожному знайденому файлі
    • sed -i: редагувати файли на місці;
    • '$d': видалити ( d) останній ( $) рядок.
    • '+': каже find, щоб тримати додавання аргументів sed(трохи ефективніше, ніж виконання команди для кожного файлу окремо, завдяки @zwol).

Якщо ви не хочете переходити до підкаталогів, тоді ви можете додати аргумент -maxdepth 1до find.


1
Хм, але на відміну від інших відповідей, це сходить у підкаталоги. (Також із сучасними версіями findцього написано більш ефективно find $dir -type f -exec sed -i '$d' '{}' '+'.)
zwol

@zwol Спасибі, я додав це у відповідь.
Наджіб Ідріссі

-print0немає в повній команді, навіщо це вводити в пояснення?
Руслан

1
Крім того, -depth 0не працює (findutils 4.4.2), замість цього має бути -maxdepth 1.
Руслан

@Ruslan У мене була перша версія, де я використовував xargs, але потім запам'ятався -exec.
Наджіб Ідріссі

9

Використовувати GNU sed -i '$d'означає прочитати повний файл і зробити його копію без останнього рядка, тоді як було б набагато ефективніше просто усікати файл на місці (принаймні для великих файлів).

З GNU truncateви можете:

for file in ./*; do
  [ -f "$file" ] &&
    length=$(tail -n 1 "$file" | wc -c) &&
    [ "$length" -gt 0 ] &&
    truncate -s "-$length" "$file"
done

Якщо файлів порівняно мало, це, ймовірно, буде менш ефективним, хоча воно виконує кілька команд на файл.

Зауважте, що для файлів, що містять додаткові байти після останнього символу нового рядка (після останнього рядка) або іншими словами, якщо вони мають не обмежений останній рядок , то, залежно від tailреалізації, tail -n 1повертаються лише ті зайві байти (як GNU tail), або останній (правильно розмежований) рядок і ці зайві байти.


Чи потрібна Вам |wc -cв tailвиклику? (або а ${#length})
Джефф Шаллер

@JeffSchaller. На жаль wc -c був призначений дійсно. ${#length}не буде працювати, оскільки він рахує символи, а не байти, і $(...)видалить символ нового рядка, так що ${#...}він буде вимкнений одним, навіть якщо всі символи були однобайтовими.
Стефан Шазелас

6

Більш портативний підхід:

for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done

Я не думаю, що це не потребує пояснень ... за винятком, можливо, що в цьому випадку dце те саме, що $dоскільки edза замовчуванням вибирає останній рядок.
Це не буде рекурсивно шукати і не обробляє приховані файли (ака dotfiles).
Якщо ви теж бажаєте відредагувати, див. Як зіставити * із прихованими файлами всередині каталогу


Приємно! Якщо ви перейдете [[]]до []цього, він буде повністю сумісним з POSIX. ( [[ ... ]]є башизм.)
Wildcard

@Wildcard - спасибі, змінено (хоча [[це не башізм )
don_crissti

Я б сказав, не-POSIX-Ism. :)
Wildcard

3

POSIX-сумісний однокласник для всіх файлів, які рекурсивно починаються в поточному каталозі, включаючи dot-файли:

find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

.txtЛише для файлів, не рекурсивно:

find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +

Також дивіться:

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