Разница между rm my-symlink и rm my-symlink/

Разница между rm my-symlink и rm my-symlink/

Просто хотел узнать, почему, если я rm -rf my-symlinkего использую, удаляется только символическая ссылка, но rm -rf my-symlink/удаляются файлы в связанном каталоге, а символическая ссылка остается?

решение1

Вы можете увидеть разницу, сравнив вывод stat my-symlinkи stat my-symlink/. my-symlink, без косой черты, — это сама символическая ссылка; my-symlink/, со слешем, — это каталог, на который указывает символическая ссылка, что можно проверить отдельно, сравнив inode my-symlink/и каталога, на который она указывает.

Имея эту информацию в руках, поведение, которое вы видите, соответствует описанному вrmспецификация: при обработке символической ссылки rmудаляет ссылку без спуска «в» нее, если она указывает на каталог; при обработке каталога (с опцией -r) рекурсивно удаляет его содержимое. В этом my-symlink/случае rmпытается удалить «каталог», но терпит неудачу, так как это не каталог, а символическая ссылка — однако из-за флага -fэто не вызывает ошибку.

решение2

Я подумал, что стоит изучить это поведение немного подробнее, поэтому вот еще один ответ.

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

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

#include <stdio.h>
#include <stdlib.h>
#include <fts.h>

int main(int argc, char* argv[])
{
    if(argc < 2) return EXIT_FAILURE;

    char* const* arr = argv + 1;
    FTS* hier = fts_open(arr, FTS_NOSTAT | FTS_PHYSICAL, NULL);

    FTSENT* ent;
    while((ent = fts_read(hier))) {
        printf("%s info=%d (D=%d DP=%d F=%d SL=%d)\n",
               ent->fts_accpath, ent->fts_info,
               ent->fts_info == FTS_D, ent->fts_info == FTS_DP,
               ent->fts_info == FTS_F || ent->fts_info == FTS_NSOK,
               ent->fts_info == FTS_SL);
    }

    fts_close(hier);
    return EXIT_SUCCESS;
}

Предположим, мы создали следующую структуру каталогов:

$ mkdir dir
$ touch dir/file
$ ln -s dir sym

Теперь давайте рассмотрим ваш первый случай и посмотрим, как FTS ведет разведку.

$ gcc fts.c 
$ ./a.out sym
sym info=12 (D=0 DP=0 F=0 SL=1)

Как вы можете видеть, в этом случае symрассматривается как файл. Символическая ссылка, если быть точнее. С этой информацией, rmследовательно, будет рассматривать его как файл и вызывать unlinkat(AT_FDCWD, "sym", 0). Последний параметр (0) заставляет unlinkatвести себя как unlink. Другими словами: он просто удаляет файл. В результате ваша ссылка исчезает.

Теперь давайте посмотрим, что происходит с sym/.

$ ./a.out sym/
sym/ info=1 (D=1 DP=0 F=0 SL=0)
file info=11 (D=0 DP=0 F=1 SL=0)
sym/ info=6 (D=0 DP=1 F=0 SL=0)

В этом случае symрассматривался как целевой каталог. Сначала мы итерируем до sym, затем sym/fileснова sym. Последнее связано с тем, как работает FTS: сначала он итерирует содержимое, затем возвращается к корневому узлу. Это на самом деле довольно удобно для rm. В первом проходе ( D) он может стирать файлы, а во втором ( DP) удалять пустые каталоги.

Как вы можете видеть, в этом случае FTS сообщает, sym/что это каталог в обоих случаях. Это потому, что мы указали путь с завершающим слешем, который заставляет ядро ​​интерпретировать его как каталог. В случае ссылки это означает, что он будет следовать по нему, несмотря ни на что.Если говорить более техническими терминами, то спецификации гласят:

Имя пути, которое содержит хотя бы один символ, отличный от косой черты, и заканчивается одним или несколькими завершающими слешами, должно обрабатываться так, как если бы к имени пути был добавлен один символ точки ('.').

Поскольку FTS сообщает sym/как каталог, rmведет себя так, как будто удаляет пустой каталог. Соответственно, он вызывает unlinkat(AT_FDCWD, "sym/", AT_REMOVEDIR). Это заставляет unlinkatвести себя как rmdir.

Однако после разрешения sym/пути unlinkatсистемный вызов поймет, что на самом деле ему не дается каталог. Поэтому он сообщит ENOTDIR, что вызовет:

$ rm: cannot remove ‘sym/’: Not a directory

И на самом деле, если вы уберете -fфлаг из своих вызовов... Это именно то, что вы увидите. Теперь, баг это или фича... Я понятия не имею.

Связанный контент