Spłaszcz katalog, ale zachowaj nazwy katalogów w nowej nazwie pliku

11

Jak spłaszczyć katalog w następującym formacie?

Przed: ./aaa/bbb/ccc.png

Po: ./aaa-bbb-ccc.png

Nathan JB
źródło
PS: Proszę również uwzględnić nazwy plików z nieparzystymi znakami (np. Spacje)
Nathan JB
3
Musisz także określić, w jaki sposób chcesz obsłużyć ewentualny przypadek, w którym ./foo/bar/baz.pngi ./foo-bar-baz.pngoba istnieją. Zakładam, że nie chcesz zastąpić tego drugiego poprzednim?
jw013,
W moim jednym przypadku jest to nieistotne, ale odpowiedź powinna rzeczywiście to wyjaśnić, aby inni mogli na nim polegać! Dzięki!
Nathan JB

Odpowiedzi:

7

Ostrzeżenie: większość tych poleceń wpisałem bezpośrednio w przeglądarce. Zastrzegający lektor.

Z zsh i zmv :

zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'

Objaśnienie: Wzorzec **/*rekurencyjnie pasuje do wszystkich plików w podkatalogach bieżącego katalogu (nie pasuje do plików w bieżącym katalogu, ale nie trzeba ich zmieniać). Pierwsze dwie pary nawiasów to grupy, do których można odwoływać się jako $1i $2w tekście zastępczym. Ostatnia para nawiasów dodaje D kwalifikator glob, dzięki czemu pliki kropek nie są pomijane. -o -ioznacza przekazanie -iopcji, aby mvużytkownik był monitowany o zastąpienie istniejącego pliku.


Tylko z narzędziami POSIX:

find . -depth -exec sh -c '
    for source; do
      case $source in ./*/*)
        target="$(printf %sz "${source#./}" | tr / -)";
        mv -i -- "$source" "${target%z}";;
      esac
    done
' _ {} +

Objaśnienie: caseinstrukcja pomija katalog bieżący i podkatalogi najwyższego poziomu bieżącego katalogu. targetzawiera nazwę pliku źródłowego ( $0) z ./usuniętymi początkowymi kreskami i wszystkie ukośniki zastąpione myślnikami, a także końcowy z. Ostatnia zjest w przypadku, gdy nazwa pliku kończy się na nowej linii: w przeciwnym razie zastąpienie jej rozebrałoby ją.

Jeśli twój findnie obsługuje -exec … +(OpenBSD, patrzę na ciebie):

find . -depth -exec sh -c '
    case $0 in ./*/*)
      target="$(printf %sz "${0#./}" | tr / -)";
      mv -i -- "$0" "${target%z}";;
    esac
' {} \;

W przypadku bash (lub ksh93) nie musisz wywoływać zewnętrznego polecenia, aby zamienić ukośniki na myślniki, możesz użyć rozszerzenia parametru ksh93 z konstrukcją zastępującą ciąg znaków ${VAR//STRING/REPLACEMENT}:

find . -depth -exec bash -c '
    for source; do
      case $source in ./*/*)
        source=${source#./}
        target="${source//\//-}";
        mv -i -- "$source" "$target";;
      esac
    done
' _ {} +
Gilles „SO- przestań być zły”
źródło
W for sourceprzykładach brakuje pierwszego prezentowanego pliku / katalogu ... W końcu dowiedziałem się, dlaczego (normalnie) użyłeś _jako $0:) ... i dziękuję za wspaniałe odpowiedzi. Wiele się od nich uczę.
Peter.O
Zauważ, że przykład zmv po prostu wykonuje próbę na sucho: wypisuje polecenia ruchu, ale ich nie wykonuje. Aby faktycznie wykonać te polecenia, usuń nw -Qn.
Peter
5

Chociaż są tutaj dobre odpowiedzi, uważam to za bardziej intuicyjne, w obrębie bash:

find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;

Gdzie aaajest twój istniejący katalog? Pliki w nim zawarte zostaną przeniesione do bieżącego katalogu (pozostawiając tylko puste katalogi w aaa). Dwa wywołania do trobsługi zarówno katalogów, jak i spacji (bez logiki dla ukośników - ćwiczenie dla czytelnika).

Uważam to za bardziej intuicyjne i stosunkowo łatwe do poprawienia. Mógłbym zmienić findparametry, jeśli powiedzmy, że chcę znaleźć tylko pliki .png. Mogę zmieniać trpołączenia lub dodawać więcej w razie potrzeby. I mogę dodać echowcześniej, mvjeśli chcę wizualnie sprawdzić, czy zrobi to dobrze (powtórz bez dodatkowych, echotylko jeśli wszystko wygląda poprawnie:

find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;

Zauważ też, że używam find aaa... a nie find .... lub find aaa/... ponieważ dwa ostatnie pozostawiłyby śmieszne artefakty w końcowej nazwie pliku.

Dave Cohen
źródło
Dzięki za ten jeden linijka - działało to dla mnie dobrze, gdy zrobiłem to spoza katalogu (dokładnie tak, jak mówiłeś). Kiedy próbowałem to zrobić z poziomu katalogu (mając nadzieję, że nie zostanie dodana nazwa katalogu głównego), wszystkie pliki „zniknęły”. Zamieniłem je w ukryte pliki w tym samym katalogu.
dgig
2
find . -mindepth 2 -type f -name '*' |
  perl -l000ne 'print $_;  s/\//-/g; s/^\.-/.\// and print' |
    xargs -0n2 mv 

Uwaga: nie będzie to działać w przypadku plików zawierających \n.
To oczywiście przenosi tylko fpliki typu ...
Jedyne zderzenia nazw mogłyby pochodzić z plików wcześniej istniejących w pwd

Testowany z tym podstawowym podzbiorem

rm -fr junk
rm -f  junk*hello*

mkdir -p  junk/junkier/junkiest
touch    'hello    hello'
touch    'junk/hello    hello'
touch    'junk/junkier/hello    hello'
touch    'junk/junkier/junkiest/hello    hello'

Wynikające z

./hello    hello
./junk-hello    hello
./junk-junkier-hello    hello
./junk-junkier-junkiest-hello    hello
Peter.O
źródło
0

Oto lswersja .. komentarze mile widziane. Działa w przypadku takich dziwacznych nazw plików "j1ळ\n\001\n/j2/j3/j4/\nh  h\n", ale naprawdę jestem zainteresowany wszelkimi pułapkami. Jest to bardziej ćwiczenie w „czy złośliwi mogą to lszrobić (solidnie)?”

ls -AbQR1p * |                        # paths in "quoted" \escaped format
    sed -n '/":$/,${/.[^/]$/p}' |     # skip files in pwd, blank and dir/ lines
    { bwd="$PWD"; while read -n1;     # save base dir strip leading "quote
                        read -r p; do # read path
        printf -vs "${p%\"*}"         # strip trailing quote"(:)
        [[ $p == *: ]] && {           # this is a directory
            cd   "$bwd/$s"            # change into new directory
            rnp="${s//\//-}"-         # relative name prefix
        } || mv "$s" "$bwd/$rnp$s"
      done; }
Peter.O
źródło
-1

Po prostu potokuj nazwę pliku + ścieżkę do trpolecenia.tr <replace> <with>

for FILE in `find aaa -name "*.png"`
do
  NEWFILE=`echo $FILE | tr '/' '-'`
  cp -v $FILE $NEWFILE
done
Emcconville
źródło
1
To nie obsługuje bezpiecznie nieparzystych nazw plików. Przestrzenie to zniszczą (między innymi).
jw013,
Na pierwszy rzut oka jest to czyste, czytelne rozwiązanie, ale @ jw013 ma rację, nie radzi sobie dobrze ze spacjami. Czy zmieniłbyś cplinię, aby cp -v "$FILE" "$NEWFILE"pomóc? (Również nie należy zakładać, że tylko pliki png.)
Nathan JB
2
@ NathanJ.Brauer Dodawanie cudzysłowów do cpwiersza jest niewystarczające. Całe podejście do analizowania findwyników za pomocą forpętli jest wadliwe. Jedynymi bezpiecznymi sposobami korzystania findsą z -execlub, -print0jeśli są dostępne (mniej przenośne niż -exec). Proponuję bardziej solidną alternatywę, ale twoje pytanie jest w tej chwili niedokładne.
jw013,
-2

Bezpieczny dla spacji w nazwach plików:

#!/bin/bash

/bin/find $PWD -type f | while read FILE
do
    _new="${FILE//\//-}"
    _new="${_new:1}"
    #echo $FILE
    #echo $_new

    /bin/mv "$FILE" "$_new"
done

Przebiegłem z cpzamiast z mvoczywistych powodów, ale powinien dać to samo.

Tim
źródło
3
type find-> find is /usr/bin/findna moim komputerze. Używanie PATHw skrypcie nazwy zmiennej jest strasznym pomysłem. Ponadto twój skrypt zmienia nazwy plików ze spacjami lub ukośnikami odwrotnymi, ponieważ niewłaściwie używasz readpolecenia (wyszukaj w tej witrynie IFS read -r).
Gilles 'SO - przestań być zły'
find is hashed (/bin/find), istotne jak? Różne dystrybucje są różne?
Tim
Wiedziałem, że powinienem był trzymać się z dala od tego, przynęty na sępa.
Tim
@Tim Zawsze możesz usunąć odpowiedź, aby odzyskać swojego przedstawiciela (i mój, a także b / c kosztuje rep do głosowania). Ścieżki kodowania do skryptów wydają się sugerować, że brakuje Ci punktu PATHzmiennej środowiskowej, z której korzysta każdy system uniksowy. Jeśli piszesz, /bin/findtwój skrypt jest faktycznie uszkodzony na każdym systemie (Gilles, moim i prawdopodobnie PO), który go nie ma /bin/find(np. B / c, w którym jest /usr/bin/find). Istotą tej PATHzmiennej środowiskowej jest, aby to nie problem.
jw013,
Nawiasem mówiąc, $(pwd)jest już dostępny w zmiennej: $PWD. Ale trzeba nie bezwzględną ścieżkę tutaj: jeśli skrypt jest wywoływany z, powiedzmy, /home/timto próbuje zmienić nazwę /home/tim/some/pathna /home-tim-some-path.
Gilles „SO- przestań być zły”