Przenieś pliki z podkatalogów do jednego katalogu i poprzedź oryginalną nazwę katalogu

3

Mam taką strukturę katalogów:

./a/1.png
./a/2.png
./a/3.png
./b/1.png
./b/2.png
./b/3.png
./c/1.png
...

I chcę wziąć wszystkie pliki z podkatalogów i przenieść je do nowego katalogu, aby ich nazwy były podobne

../dest/a_1.png
../dest/a_2.png
../dest/a_3.png
../dest/b_1.png
../dest/b_2.png
../dest/b_3.png
../dest/c_1.png
...

Najbliższe, jakie udało mi się znaleźć bez pisania skryptu, aby to zrobić plik po pliku, to skorzystanie findz --backup=numberedopcji, która skropliłaby moje pliki do jednego katalogu, ale ostatecznie straciłaby kontekst katalogu z nazwy pliku.

Czy istnieje zwięzły sposób na osiągnięcie tego?

Brad Dwyer
źródło
1
Dlaczego nie napisać skryptu?
var firstName
W końcu napisałem (super nieefektywny) skrypt (w nodejs .. to wiem najlepiej), ale miałem nadzieję nauczyć się czegoś nowego :)
Brad Dwyer

Odpowiedzi:

8

Za pomocą niezależnego renamepolecenia Perla :

rename -n 's|/|_|; s|^|dest/|' */*.png

Wynik:

a / 1.png przemianowano na dest / a_1.png
a / 2.png przemianowano na dest / a_2.png
a / 3.png przemianowano na dest / a_3.png
b / 1.png przemianowany na dest / b_1.png
b / 2.png przemianowano na dest / b_2.png
b / 3.png przemianowano na dest / b_3.png
c / 1.png przemianowany na dest / c_1.png

Jeśli wszystko wygląda dobrze, usuń opcję -n.

Cyrus
źródło
1
Heh, bardzo sprytny, po prostu zastąpienie /go _wykonuje pracę w tym konkretnym przypadku, oczywiście.
slhck,
Fajnie, nie wiedziałem o Perlu rename- wygląda na użyteczne!
Brad Dwyer,
Możliwe mmvjest także inne podejście Jochena Lutza z :rename -n 's|(.*)/(.*)|dest/$1_$2|' */*.png
Cyrus
6

Z Bash 4 i rekurencyjnym globowaniem ( shopt -s globstar):

for f in **/*.png; do
  dn=$(basename "$(dirname "$f")")
  bn=$(basename "$f")
  mv -- "$f" "../dest/${dn}_${bn}"
done

Zauważ, że dirname /foo/bar/bazzwraca, /foo/bara nie tylko bar, dlatego musisz wywołać basenamewynik, aby dostać się barna wypadek, gdybyś pracował z innego folderu nadrzędnego.

slhck
źródło
3

Jako alternatywne podejście, nie trzeba zewnętrznych programów takich jak rename, basenameitp - to wszystko może być obsługiwane w ramach bashdopełniania parametrów: -

find SourceDir ... | while read -r f; do mv "$f" "TargetDir/${f//\//_}"; done

Rozszerzenie jest nieco trudne do śledzenia, więc oto co się stanie: -

  • Pliki do przeniesienia można znaleźć za pomocą find.
  • Każda nazwa pliku jest odczytywana kolejno $f.
  • read -rParametr a cudzysłowie rozwinięć obsłużyć dziwne nazwy spacjami w nazwach plików.
  • mvPolecenie przenosi nazwiska jak a/b/cdo TargetDir/a_b_c.
  • Ekspansja cel zastępuje każdy /za _, ale wygląda na trudne, ponieważ /jest częścią składni substytucyjnego.
  • Ogólna forma to ${param/old/new}, która zastępuje pierwszą instancję oldprzez neww rozszerzeniu.
  • Formularz potrzebny jest tutaj ${param//old/new}, które zastępuje każde wystąpienie oldprzez neww ekspansji.
  • Aby /być częścią starego ciągu znaków, musi być poprzedzony znakiem ucieczki, ponieważ \/stąd jest raczej niejasny ${f//\//_}: pierwszy /wprowadza składnię podstawiania, drugi /określa zastąpienie co , trzeci (znak zmiany znaczenia) /jest starym ciągiem, a ostatni /wprowadza nowy ciąg ( _).

Często nie widzę tej formy rozszerzenia w skryptach, ale warto o tym wiedzieć, ponieważ czasami może być bardzo przydatna.

Istnieje kilka nazw plików, które to zepsują (osadzone znaki nowej linii oraz spacje początkowe lub końcowe, chociaż istnieją dwa sposoby na obejście tych ostatnich: -

  • Użyj read -rzamiast read -r fi użyj REPLYzamiast f.
  • Użyj while f="$(line)"zamiast read -r f.

Ten ostatni jest ładniejszy, ale korzysta z zewnętrznego programu line, który może nie być dostępny we wszystkich systemach, choć można go zakodować jako funkcję:

line() { read -r; r=$?; echo "$REPLY"; return $r; }
AFH
źródło
Re „wszystko można obsłużyć w ramach rozszerzenia parametrów bash”, co raczej zakłada, że ​​używasz bash, prawda? Podczas gdy Perl i c są niezależne od powłoki, AFAIK.
jamesqf
1
@jamesqf - Pytający zawarty bashjako tag, stąd założenie.
AFH
2

Alternatywą dla zmiany nazwy jest mmv , który wykorzystuje standardowe wzorce powłoki.

Ze strony podręcznika:

MMV porusza się (lub kopie, dołącza lub linki, jak określono) każdy plik źródłowy dopasowywania od wzoru do nazwy docelowej określonej przez do wzorca. Ta wielokrotna akcja jest wykonywana bezpiecznie, tzn. Bez nieoczekiwanego usunięcia plików z powodu kolizji nazw docelowych z istniejącymi nazwami plików lub innymi nazwami docelowymi. Ponadto, zanim cokolwiek zrobisz , mmv próbuje wykryć wszelkie błędy, które mogłyby wyniknąć z całego zestawu określonych działań i daje użytkownikowi wybór albo kontynuowania, unikając obrażających się części, albo przerywania.

W przypadku użycia:

mmv -n '*/*' 'dest/#1_#2'
a/1.jpg -> dest/a_1.jpg
a/2.jpg -> dest/a_2.jpg
a/3.jpg -> dest/a_3.jpg
b/1.jpg -> dest/b_1.jpg
b/2.jpg -> dest/b_2.jpg
b/3.jpg -> dest/b_3.jpg
c/1.jpg -> dest/c_1.jpg
c/2.jpg -> dest/c_2.jpg
c/3.jpg -> dest/c_3.jpg

Podobnie jak zmiana nazwy , „-n” nie jest wykonywany. Usuń go, aby zmienić nazwę.

Oprócz zmiany nazwy, mmv obsługuje także kopiowanie, łączenie lub dołączanie plików.

Jochen Lutz
źródło