Сценарии оболочки UNIX: как рекурсивно переместить файлы вверх на один каталог?

8

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

/A/B/C/f

Существует 11 каталогов на уровне A, каждый из которых содержит около 100 каталогов на уровне B, каждый из которых содержит около 30 каталогов на уровне C, каждый из которых содержит один файл f.

Как мне переместить все файлы на один уровень вверх? Например, учитывая этот набор файлов ...

/ A / B / C / f1
/ A / B / C / f2
/ A / B / C / f3
/ A / B / C / f4

Я хочу, чтобы каталог /A/B/содержал 4 файла, от f1 до f4. Удаление каталогов C не является необходимым.

Я надеюсь , что это решаемая проблема, возможно , с привлечением find, xargsи whatnot. Любые идеи?

Ура,

Джеймс

jl6
источник

Ответы:

9

Это довольно просто с GNU find (как найдено в Linux) или любым другим find, который поддерживает -execdir:

find A -type f -execdir mv -i {} .. \;

Со стандартом find:

find A -type f -exec sh -c 'mv -i "$1" "${1%/*}/.."' sh {} \;

С зш:

zmv -Q -o-i 'A/(**/)*/(*)(.)' 'A/$1$2'

Если структура каталогов всегда имеет одинаковый уровень вложенности, рекурсивный обход не требуется (но сначала удалите пустые каталоги):

for x in */*; do; echo mv -i "$x"/*/* "$x"/..; done
Жиль "ТАК - перестань быть злым"
источник
1

Для этого набора файлов это будет сделано:

$ cd /A/B/C/
$ mv ./* ../

Но я ожидаю, что ваша проблема несколько сложнее ... Я не могу ответить на это ... Я не совсем уверен, какова ваша структура режиссера ... Не могли бы вы уточнить?

Переполнение стека мертв
источник
Очевидно, это будет работать для любого каталога / A / B / C, но, поскольку у меня есть около 30000 таких каталогов, я надеюсь, что команда, которая может быть выполнена на вершине иерархии, позаботится обо всех них сразу.
JL6
1
@ jl6 Понятно, а перемещать нужно только файлы? Таким образом, C не должен переходить в B, а B не в A и т. Д.
Переполнение стека не работает
Это правильно - операция должна выполняться только с файлами на самом низком уровне иерархии, и их следует перемещать только на один уровень вверх.
16
0

Мое первое предположение

$ find A -type f -exec mv {} .. \;  

до тех пор, пока вы не укажете, -depthвсе должно быть в порядке. Я НЕ пробовал, поэтому сначала проверьте его, прежде чем совершить это.

Хотей
источник
Что делает находка, когда есть два файла с одинаковым именем?
Найти не проблема, встроенная команда "mv" есть. Названия дублирования не будут хороши, поскольку mv не собирается переименовывать, он перезаписывает. Вы можете использовать mv -i, чтобы немного помочь, но с более чем 30 000 файлами, которые могут быть болезненными. Если вам нужно как можно больше контролировать ситуацию, вам может понадобиться программа, написанная на языке сценариев (я бы выбрал python, YMMV).
Хотей
Я не использовал «резервные копии» с mv, но, глядя на справочную страницу, похоже, это могло бы решить вашу проблему с двойными именами. "find A -type f -exec mv --backup numbered {} .. \;" может сработать (обратите внимание, это двойная черта в --backup)
Хотей
Приведенный пример поиска переместит все файлы на самом низком уровне в каталог выше A (я полагаю, что «..» интерпретируется оболочкой перед передачей в поиск / mv?). Мне нужны файлы, чтобы переместиться только на один уровень вверх в иерархии. Еще одно уточнение: повторяющихся имен файлов не будет, если файлы перемещаются только на ОДИН уровень вверх.
JL6
@hotei: mvкоманды выполняются в текущем каталоге, поэтому ..не делайте то, что вы надеетесь. Смотрите мой ответ для решений. @ jl6: оболочка не имеет ничего общего с ..ядром.
Жиль "ТАК - перестать быть злым"
0

Если вы хотите переместить только файлы, находящиеся в конечных каталогах (т.е. вы не хотите перемещать /A/B/fileих, /Aесли они Bсодержат подкаталоги), то вот несколько способов сделать это:

Оба требуют этого

leaf ()
{
    find $1 -depth -type d | sed 'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'
}
shopt -s nullglob

Этот работает:

leaf A | while read -r dir
do
    for file in "$dir"/*
    do
        parent=${dir%/*}
        if [[ -e "$parent/${file##*/}" ]]
        then
            echo "not moved: $file"
        else
            mv "$file" "$parent"
        fi
    done
done

Это будет быстрее, но оно не любит пустые исходные каталоги:

leaf A | while read -r dir
do
    mv -n "${dir}"/* "${dir%/*}"
    remains=$(echo "$dir"/*)
    [[ -n "$remains" ]] && echo "not moved: $remains"
done
Приостановлено до дальнейшего уведомления.
источник