xargs и find, rm жалуется на \ n (перевод строки) в имени файла - PullRequest
5 голосов
/ 15 декабря 2011

Я пытаюсь удалить самый старый файл в дереве с помощью скрипта в Debian.

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | xargs -0 ls -t | tail -1 | xargs -0 rm

Но я получаю сообщение об ошибке:

rm: cannot remove `/home/backups/tree/structure/file.2011-12-08_03-01-01.sql.gz\n': No such file or directory

Любые идеи, что я делаю неправильно (или есть более простой / лучший способ?), Я пытался RTFM, но потерял.

Ответы [ 7 ]

11 голосов
/ 15 декабря 2011

ls добавляет символ новой строки, а последний xargs -0 говорит, что символ новой строки является частью имени файла. Запустите последние xargs с -d '\n' вместо -0.

Кстати, благодаря тому, как работает xargs, вся ваша труба - ошибка, ожидающая своего появления. Рассмотрим действительно длинный список имен файлов, созданный find, так что xargs -0 ls запускает ls несколько раз с подмножествами имен файлов. Только самый старый из последний ls вызов пройдет мимо tail -1. Если на самом деле самый старый файл, скажем, самое первое имя файла, выводимое find, вы удаляете младший файл.

3 голосов
/ 15 декабря 2011

Любое решение с участием ls абсолютно неверно.

Правильный способ сделать это - использовать find для извлечения набора файлов, sort, чтобы упорядочить их в хронологическом порядке, отфильтровать все, кроме первого, затем rm для удаления. @ Кен был в основном прав, упустив лишь несколько деталей.

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %p\0' |\
    sort -z -n | \
    { IFS= read -d '' file ; [ -n "$file" ] && echo rm -f "$(cut -d' ' -f2- <<<"$file")" ; }

Удалите echo выше, чтобы фактически выполнить удаление.

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

Если вас не волнует разрыв строк в именах файлов, это становится немного проще

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -printf '%T@ %p\n' |\
    sort -n |\
    head -n 1 |\
    cut -d' ' -f2- |\
    xargs echo rm

Разница в том, что мы можем положиться на head и можем использовать cut на трубе вместо того, чтобы делать что-то сумасшедшее.

2 голосов
/ 15 декабря 2011

Вы также можете использовать find для распечатки времени модификации, сортировки, вырезания и xargs по желанию:

find /home/backups -printf "%T@ %p\n" | sort -n | head -1 | cut -d" " -f2- | xargs ls -al
1 голос
/ 15 декабря 2011

ls испускает символы новой строки в качестве разделителей, поэтому вам нужно заменить второй xargs -0 на xargs -d '\n'. Разрывается, однако, если в самом старом файле есть новая строка в имени.

0 голосов
/ 05 августа 2012
find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | xargs -0 stat --format '%010Y:%n' | sort -n | head -n 1 | cut -d: -f2- | xargs -d '\n' rm 

из: Сортировка списка файлов по дате в Linux (включая подкаталоги)

0 голосов
/ 15 декабря 2011

ls -tr $(find /home/backups -name '*.gz' -o -name '*.tgz')|head -1|xargs rm -f

0 голосов
/ 15 декабря 2011

Редактировать Я пропустил точку ls -t.

Могу ли я предложить сделать это намного проще, например,

find /home/backups \
    -type f -iregex '.*\.t?gz$' \
    -mtime +60 -exec rm {} \;

, который удалит любой соответствующий файл старше определенного возраста (в примере 60 дней)


Вы использовали tail, но не указали его для поиска нулевых разделителей.

В любом случае, вот утилита, которую вы можете использовать для возврата последнего элемента, разделенного 0:

#include <string>
#include <iostream>
#include <cstdio>

int main(int argc, const char *argv[])
{
    std::cin.unsetf(std::ios::skipws);
    if (!  (freopen(NULL, "wb", stdout) && freopen(NULL, "rb", stdin) ))
    {
        perror("Cannot open stdout/in in binary mode");
        return 255;
    }

    std::string previous, element;
    while (std::getline(std::cin, element, '\0'))
    {
        previous = element; 
        // if you have c++0x support, use this _instead_ for performance:
        previous = std::move(element);
    }

    std::cout << previous << '\0' << std::flush;
}

Используйте его как

find /home/backups -type f \( -name \*.tgz -o -name \*.gz \) -print0 | ./mytail | xargs -0 rm 
...