Zachowaj strukturę katalogów podczas przenoszenia plików za pomocą funkcji find

22

Utworzyłem następujący skrypt, który przenosi pliki starych dni, zgodnie z definicją z katalogu źródłowego do katalogu docelowego. Działa idealnie.

#!/bin/bash

echo "Enter Your Source Directory"
read soure

echo "Enter Your Destination Directory"
read destination 

echo "Enter Days"
read days



 find "$soure" -type f -mtime "-$days" -exec mv {} "$destination" \;

  echo "Files which were $days Days old moved from $soure to $destination"

Ten skrypt świetnie przenosi pliki, przenosi także pliki z podkatalogu źródłowego, ale nie tworzy podkatalogu do katalogu docelowego. Chcę zaimplementować w nim tę dodatkową funkcję.

z przykładem

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

Kiedy uruchamiam ten skrypt, przenosi on również pliki hex w katalogu maxi, ale potrzebuję, aby ten sam hex został utworzony w katalogu maxi i tam przenosił pliki w tym samym hexie.

Ketan Patel
źródło

Odpowiedzi:

13

Zamiast uruchamiania mv /home/ketan/hex/foo /home/maximusisz zmienić katalog docelowy na podstawie ścieżki utworzonej przez find. Jest to łatwiejsze, jeśli najpierw przejdziesz do katalogu źródłowego i uruchomisz find .. Teraz możesz po prostu dodać katalog docelowy do każdego elementu wyprodukowanego przez find. Musisz uruchomić powłokę w find … -execpoleceniu, aby wykonać konkatenację i, jeśli to konieczne, utworzyć katalog docelowy.

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  mkdir -p "$0/${1%/*}"
  mv "$1" "$0/$1"
' "$destination" {} \;

Pamiętaj, że aby uniknąć problemów z cytowaniem, jeśli $destinationzawiera znaki specjalne, nie można po prostu zastąpić go w skrypcie powłoki. Możesz wyeksportować go do środowiska, aby osiągnął wewnętrzną powłokę, lub możesz przekazać go jako argument (to właśnie zrobiłem). Możesz zaoszczędzić trochę czasu na wykonaniu, grupując shpołączenia:

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  for x do
    mkdir -p "$0/${x%/*}"
    mv "$x" "$0/$x"
  done
' "$destination" {} +

Alternatywnie, w zsh, można skorzystać z zmvfunkcji , a .i m kwalifikacyjne glob to tylko dopasować zwykłe pliki w odpowiednim przedziale czasowym. Musisz przekazać alternatywną mvfunkcję, która najpierw tworzy katalog docelowy, jeśli to konieczne.

autoload -U zmv
mkdir_mv () {
  mkdir -p -- $3:h
  mv -- $2 $3
}
zmv -Qw -p mkdir_mv $source/'**/*(.m-'$days')' '$destination/$1$2'
Gilles „SO- przestań być zły”
źródło
for x do, brakuje ci ;tam :). Poza tym nie mam pojęcia, co chciałbyś osiągnąć, $0ale jestem przekonany, że tak będzie sh:).
Michał Górny,
@ MichałGórny for x; dotechnicznie nie jest zgodny z POSIX (sprawdź gramatykę ), ale nowoczesne powłoki pozwalają na jedno for x doi drugie for x; do; niektóre stare muszle Bourne'a się nie zmokły for x; do. Na nowoczesnych powłokach, z sh -c '…' arg0 arg1 arg2 arg3, arg0staje się $0, arg1staje się $1itp. Jeśli chcesz $0być sh, musisz pisać sh -c '…' sh arg1 arg2 arg3. Ponownie niektóre powłoki Bourne'a zachowywały się inaczej, ale POSIX to określa.
Gilles 'SO - przestań być zły'
pushdwydaje się lepszym wyborem cd, ponieważ jest mniej ingerujący w obecne środowisko.
jpmc26,
16

Wiem, że findzostało określone, ale brzmi to jak praca dla rsync.

Najczęściej używam następujących:

rsync -axuv --delete-after --progress Source/ Target/

Oto dobry przykład, jeśli chcesz przenosić tylko pliki określonego typu ( przykład ):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/
ryanjdillon
źródło
dużo czystsze niż inne rozwiązania :)
Guillaume
2
Okazało się, że --remove-source-filesbyło to pomocne, co skutecznie powoduje przenoszenie plików zamiast ich kopiowania. To niesamowite wykorzystanie rsync.
Tom
3

możesz to zrobić za pomocą dwóch instancji find (1)

Zawsze jest cpio (1)

(cd "$soure" && find  | cpio -pdVmu "$destination")

Sprawdź argumenty dla CPIO. Te, które dałem

Mark Lamourine
źródło
1
Spowoduje to uszkodzenie wszystkich nazw plików z białymi znakami.
Chris Down
1
Spowoduje to skopiowanie plików zamiast ich przenoszenia.
Gilles „SO- przestań być zły”
Może nie jest to idealna odpowiedź, ale pomogła mi przenieść pliki z zachowaniem ścieżek w mniej lub bardziej prosty sposób (mam wystarczająco dużo miejsca, aby skopiować pliki, a następnie je usunąć). Upvoted
AhHatem
3

To nie jest tak wydajne, ale kod jest łatwiejszy do odczytania i zrozumienia, moim zdaniem, jeśli po prostu skopiujesz pliki, a następnie usuniesz je.

find /original/file/path/* -mtime +7 -exec cp {} /new/file/path/ \;
find /original/file/path/* -mtime +7 -exec rm -rf {} \;

Uwaga: Błąd wykryty przez @MV w operacjach automatycznych:

Korzystanie z dwóch osobnych operacji jest ryzykowne. Jeśli niektóre pliki staną się starsze niż 7 dni podczas wykonywania operacji kopiowania, nie zostaną one skopiowane, ale zostaną usunięte podczas operacji usuwania. W przypadku ręcznego wykonania raz może to nie stanowić problemu, ale w przypadku skryptów automatycznych może to prowadzić do utraty danych

Elliott Post
źródło
2
Korzystanie z dwóch osobnych operacji jest ryzykowne. Jeśli niektóre pliki staną się starsze niż 7 dni podczas wykonywania operacji kopiowania, nie zostaną one skopiowane, ale zostaną usunięte podczas operacji usuwania. W przypadku ręcznego wykonania raz może to nie stanowić problemu, ale w przypadku skryptów automatycznych może to prowadzić do utraty danych.
MV.
2
Łatwym rozwiązaniem tego problemu jest uruchomienie wyszukiwania raz, zapisanie listy jako pliku tekstowego, a następnie dwukrotne użycie xargs, aby wykonać kopię, a następnie usunąć.
David M. Perlman
1

Możesz to zrobić, dodając bezwzględną ścieżkę do pliku zwróconego przez findścieżkę docelową:

find "$soure" -type f -mtime "-$days" -print0 | xargs -0 -I {} sh -c '
    file="{}"
    destination="'"$destination"'"
    mkdir -p "$destination/${file%/*}"
    mv "$file" "$destination/$file"'
Chris Down
źródło
Chris przenosi teraz cały dom do maksimum
Ketan Patel
@ K.KPatel Nie, nie ma. Po prostu utrzymuje strukturę katalogu.
Chris Down
Dzieje się tak, jeśli $destinationzawiera znaki specjalne, ponieważ ulega rozszerzeniu w wewnętrznej powłoce. Może miałeś na myśli destination='\'"$destination"\''? To wciąż się psuje '. Tworzy to również pliki takie jak /home/maxi/home/ketan/hex/foozamiast /home/maxi/hex/foo.
Gilles „SO- przestań być zły”
0

Lepsze (najszybsze i bez zajmowania przestrzeni dyskowej przez kopiowanie zamiast przenoszenia), nazwy plików nie mają także wpływu, jeśli zawierają one znaki specjalne:

export destination
find "$soure" -type f "-$days" -print0 | xargs -0 -n 10 bash -c '
for file in "$@"; do
  echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
  mkdir -p "$destination"/`dirname "$file"`
  \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
done'

Lub szybciej, przenosząc kilka plików w tym samym czasie dla wielu procesorów, używając polecenia „równoległego”:

echo "Move oldest $days files from $soure to $destination in parallel (each 10 files by "`parallel --number-of-cores`" jobs):"
function move_files {
  for file in "$@"; do
    echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
    mkdir -p "$destination"/`dirname "$file"`
    \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
  done
}
export -f move_files
export destination
find "$soure" -type f "-$days" -print0 | parallel -0 -n 10 move_files

PS: Masz literówkę, „soure” powinno być „source”. Zachowałem nazwę zmiennej.

Bogdan Velcea
źródło
0

Jest to mniej eleganckie, ale łatwe, jeśli liczba / rozmiar plików nie jest zbyt duża

Spakuj pliki razem do ziparchiwum, a następnie rozpakuj w miejscu docelowym bez -jopcji. Domyślnie zip utworzy względną strukturę katalogów.

Chris Johnson
źródło
0

Wypróbuj w ten sposób:

IFS=$'\n'
for f in `find "$soure" -type f -mtime "-$days"`;
do
  mkdir -p "$destination"/`dirname $f`;
  mv $f "$destination"/`dirname $f`;
done
Alessio
źródło
0

Ponieważ wydaje się, że nie ma naprawdę łatwego rozwiązania tego problemu i bardzo go potrzebuję, stworzyłem to narzędzie open source dla systemu Linux (wymaga Pythona): https://github.com/benapetr/smv

Istnieje wiele sposobów wykorzystania go do osiągnięcia tego, czego potrzebujesz, ale prawdopodobnie najprostszym byłoby coś takiego:

 # -vf = verbose + force (doesn't stop on errors)
smv -vf `find some_folder -type f -mtime "-$days"` target_folder

Możesz dodatkowo uruchomić go w trybie suchym, aby nie robił nic poza drukowaniem tego, co by zrobił

smv -rvf `find some_folder -type f -mtime "-$days"` target_folder

Lub jeśli lista plików jest zbyt długa, aby zmieścić się w linii argumentów i nie przeszkadza ci wykonywanie Pythona dla każdego pliku, to

find "$soure" -type f -mtime "-$days" -exec smv {} "$destination" \;
Petr
źródło