Skrypty powłoki UNIX: jak rekurencyjnie przenosić pliki do jednego katalogu?

8

Mam dużą liczbę małych plików, f, ułożonych w strukturę katalogów w następujący sposób:

/A/B/C/f

Istnieje 11 katalogów na poziomie A, każdy z około 100 katalogami na poziomie B, każdy z około 30 katalogami na poziomie C, każdy z jednym plikiem f.

Jak przenieść wszystkie pliki o jeden poziom wyżej? Na przykład, biorąc pod uwagę ten zestaw plików ...

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

Chcę, aby katalog /A/B/zawierał 4 pliki, od f1 do f4. Usunięcie katalogów C nie jest konieczne.

Mam nadzieję, że jest to rozwiązanie problemu, ewentualnie z udziałem find, xargsi whatnot. Jakieś pomysły?

Twoje zdrowie,

James

jl6
źródło

Odpowiedzi:

9

Jest to dość proste w przypadku znalezienia GNU (jak w Linuksie) lub dowolnego innego znalezienia, które obsługuje -execdir:

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

Ze standardem find:

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

Z zsh:

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

Jeśli struktura katalogów ma zawsze ten sam poziom zagnieżdżenia, nie potrzebujesz rekurencyjnego przechodzenia (ale najpierw usuń puste katalogi):

for x in */*; do; echo mv -i "$x"/*/* "$x"/..; done
Gilles „SO- przestań być zły”
źródło
1

W przypadku tego zestawu plików zrobiłoby to:

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

Ale spodziewam się, że twój problem jest nieco bardziej skomplikowany ... Nie mogę na to odpowiedzieć ... Nie jestem do końca pewien, jaka jest twoja struktura katalogu ... Czy możesz to wyjaśnić?

Przepełnienie stosu nie działa
źródło
Będzie to oczywiście działało dla każdego katalogu / A / B / C, ale ponieważ mam około 30000 takich katalogów, mam nadzieję na polecenie, które można wykonać na górze hierarchii, aby zająć się nimi wszystkimi naraz.
jl6
1
@ jl6 Rozumiem i tylko pliki wymagają przeniesienia? Więc C nie musi przejść do B, a B nie do A itp.
Przepełnienie stosu nie działa
Zgadza się - operacja powinna być wykonywana tylko na plikach, na najniższym poziomie hierarchii i powinny być przenoszone tylko o jeden poziom wyżej.
jl6
0

Moje pierwsze przypuszczenie to

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

tak długo, jak nie określisz -depth, powinno być w porządku. NIE próbowałem tego, więc przetestuj go najpierw, zanim się na to zgodzisz.

hotei
źródło
Co robi find, gdy istnieją dwa pliki o tej samej nazwie?
Znajdź nie jest problemem, wbudowane polecenie „mv” to. Zduplikowane nazwy nie byłyby dobre, ponieważ mv nie zamierza zmienić nazwy, zastąpi ją. Możesz użyć mv -i, aby trochę pomóc, ale z ponad 30 000 plików, które mogą być bolesne. Jeśli potrzebujesz takiej kontroli nad sytuacją, możesz potrzebować programu napisanego w języku skryptowym (moim wyborem byłby python, YMMV).
hotei
Nie używałem „kopii zapasowych” z mv, ale patrząc na stronę podręcznika wygląda na to, że może to być rozwiązanie twojego problemu z kopiowaniem nazw. msgstr "znajdź A -type f -exec mv --backup numbered {} .. \;" może działać (pamiętaj, że to podwójne
myślenie
Podany przykład znajdź przeniesie wszystkie pliki na najniższym poziomie do katalogu znajdującego się powyżej A (uważam, że „..” jest interpretowany przez powłokę przed przekazaniem do find / mv?). Potrzebuję plików, aby przejść o jeden poziom wyżej w hierarchii. Jeszcze jedno wyjaśnienie: nie będzie zduplikowanych nazw plików, o ile pliki przesuwają się tylko o JEDEN poziom w górę.
jl6
@hotei: mvPolecenia są wykonywane w bieżącym katalogu, więc ..nie robi tego, na co masz nadzieję. Zobacz moją odpowiedź na rozwiązania. @ jl6: powłoka nie ma nic wspólnego z ..tym, co obsługuje jądro.
Gilles „SO- przestań być zły”
0

Jeśli chcesz tylko do przenoszenia plików, które są w katalogach liści (czyli nie chcesz, aby przejść /A/B/filedo /Ajeżeli Bzawiera podkatalogi), to oto kilka sposobów, aby to zrobić:

Oba tego wymagają

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

Ten działa:

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

Będzie to szybsze, ale nie lubi pustych katalogów źródłowych:

leaf A | while read -r dir
do
    mv -n "${dir}"/* "${dir%/*}"
    remains=$(echo "$dir"/*)
    [[ -n "$remains" ]] && echo "not moved: $remains"
done
Wstrzymano do odwołania.
źródło