Jak spłaszczyć katalog w następującym formacie?
Przed: ./aaa/bbb/ccc.png
Po: ./aaa-bbb-ccc.png
./foo/bar/baz.png
i ./foo-bar-baz.png
oba istnieją. Zakładam, że nie chcesz zastąpić tego drugiego poprzednim?
Jak spłaszczyć katalog w następującym formacie?
Przed: ./aaa/bbb/ccc.png
Po: ./aaa-bbb-ccc.png
./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 :
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 $1
i $2
w tekście zastępczym. Ostatnia para nawiasów dodaje D
kwalifikator glob, dzięki czemu pliki kropek nie są pomijane. -o -i
oznacza przekazanie -i
opcji, aby mv
uż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: 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ńcowy z
. Ostatnia z
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):
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
' _ {} +
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
:
find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;
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 do tr
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:
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.
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 f
pliki 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
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ą to ls
zrobić (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; }
Po prostu potokuj nazwę pliku + ścieżkę do tr
polecenia.tr <replace> <with>
for FILE in `find aaa -name "*.png"`
do
NEWFILE=`echo $FILE | tr '/' '-'`
cp -v $FILE $NEWFILE
done
cp
linię, aby cp -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 analizowania find
wyników za pomocą for
pętli jest wadliwe. Jedynymi bezpiecznymi sposobami korzystania find
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:
#!/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 cp
zamiast z mv
oczywistych powodów, ale powinien dać to samo.
type find
-> find is /usr/bin/find
na moim komputerze. Używanie PATH
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żywasz read
polecenia (wyszukaj w tej witrynie IFS 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ą tej PATH
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
.