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.
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 Dkwalifikator 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
' _ {} +
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:
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:
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.
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
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; }
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.
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.
./foo/bar/baz.png
i./foo-bar-baz.png
oba istnieją. Zakładam, że nie chcesz zastąpić tego drugiego poprzednim?Odpowiedzi:
Ostrzeżenie: większość tych poleceń wpisałem bezpośrednio w przeglądarce. Zastrzegający lektor.
Z zsh i zmv :
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$1
i$2
w tekście zastępczym. Ostatnia para nawiasów dodajeD
kwalifikator glob, dzięki czemu pliki kropek nie są pomijane.-o -i
oznacza przekazanie-i
opcji, abymv
użytkownik był monitowany o zastąpienie istniejącego pliku.Tylko z narzędziami POSIX:
Objaśnienie:
case
instrukcja pomija katalog bieżący i podkatalogi najwyższego poziomu bieżącego katalogu.target
zawiera nazwę pliku źródłowego ($0
) z./
usuniętymi początkowymi kreskami i wszystkie ukośniki zastąpione myślnikami, a także końcowyz
. Ostatniaz
jest w przypadku, gdy nazwa pliku kończy się na nowej linii: w przeciwnym razie zastąpienie jej rozebrałoby ją.Jeśli twój
find
nie obsługuje-exec … +
(OpenBSD, patrzę na ciebie):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}
:źródło
for source
przykł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ę.Chociaż są tutaj dobre odpowiedzi, uważam to za bardziej intuicyjne, w obrębie
bash
:Gdzie
aaa
jest 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 dotr
obsł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ć
find
parametry, jeśli powiedzmy, że chcę znaleźć tylko pliki .png. Mogę zmieniaćtr
połączenia lub dodawać więcej w razie potrzeby. I mogę dodaćecho
wcześniej,mv
jeśli chcę wizualnie sprawdzić, czy zrobi to dobrze (powtórz bez dodatkowych,echo
tylko jeśli wszystko wygląda poprawnie:Zauważ też, że używam
find aaa
... a niefind .
... lubfind aaa/
... ponieważ dwa ostatnie pozostawiłyby śmieszne artefakty w końcowej nazwie pliku.źródło
Uwaga: nie będzie to działać w przypadku plików zawierających
\n
.To oczywiście przenosi tylko
f
pliki typu ...Jedyne zderzenia nazw mogłyby pochodzić z plików wcześniej istniejących w pwd
Testowany z tym podstawowym podzbiorem
Wynikające z
źródło
Oto
ls
wersja .. 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ą tols
zrobić (solidnie)?”źródło
Po prostu potokuj nazwę pliku + ścieżkę do
tr
polecenia.tr <replace> <with>
źródło
cp
linię, abycp -v "$FILE" "$NEWFILE"
pomóc? (Również nie należy zakładać, że tylko pliki png.)cp
wiersza jest niewystarczające. Całe podejście do analizowaniafind
wyników za pomocąfor
pętli jest wadliwe. Jedynymi bezpiecznymi sposobami korzystaniafind
są z-exec
lub,-print0
jeśli są dostępne (mniej przenośne niż-exec
). Proponuję bardziej solidną alternatywę, ale twoje pytanie jest w tej chwili niedokładne.Bezpieczny dla spacji w nazwach plików:
Przebiegłem z
cp
zamiast zmv
oczywistych powodów, ale powinien dać to samo.źródło
type find
->find is /usr/bin/find
na moim komputerze. UżywaniePATH
w 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żywaszread
polecenia (wyszukaj w tej witrynieIFS read -r
).find is hashed (/bin/find)
, istotne jak? Różne dystrybucje są różne?PATH
zmiennej środowiskowej, z której korzysta każdy system uniksowy. Jeśli piszesz,/bin/find
twó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ą tejPATH
zmiennej środowiskowej jest, aby to nie problem.$(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/tim
to próbuje zmienić nazwę/home/tim/some/path
na/home-tim-some-path
.