Zdarza się, że przyjdzie nam do głowy nazwać pliki wykorzystując dwukropki, np. ładnie formatując dane. Pod systemem plików ext nic się nie dzieje, ale gdy zrobimy to na współdzielonej z Windowsem partycji NTFS to po starcie Windy przywita nas chkdsk robiąc na dysku sieczkarnię…
Naszym zadaniem jest szybka zamiana dwukropków i innych znaków zakazanych we wszystkich plikach - najlepiej rekursywnie. A jeszcze lepiej mieć wybór 😉 Więc po kolei:
Najpierw zajmiemy się obsłużeniem znaku na jaki podmieniamy. Ponieważ nie chce się nikomu co chwila dopisywać parametru “_”, więc wypada obsłużyć nulla - “-z” sprawdza, czy zmienna jest pusta i zwraca true jeśli nic nie podstawiono. Zmienna $k jest tu pomocnicza.
if [ -z $1 ]
then
k="_"
elif [ "$1" == "help" ] || [ "$1" == "--help" ] || [ "$1" == "-h" ]
then
#tekst pomocy
exit
else
k=$1
fi
Obsługa rekurencji katalogowej jest o tyle ciekawa, że to co jest w zmiennej zostanie potem wykonane - “eval #{nazwa_zmiennej}”.
if [ -z $2 ] || [ "$2" != "-" ]
then
r="find . -name '*'"
else [ "$2" == "-" ]
r="find . -maxdepth 1 -name '*'"
fi
Czas na gwóźdź do trumny programisty - REGEXPY, czyli po ludzku mówiąc wyrażenia regularne. Większość programistów ten temat omija szerokim łukiem dlatego powstały strony w rodzaju http://www.regular-expressions.info/reference.html, gdzie znajdziemy listę znaków nas interesujących. Składnia sed’a jest w rodzaju “s/co/na_co/coś”. To “coś” to zwykle g - jeśli chcemy podmienić wszystkie wystąpienia. Chyba.
Poniższe piękne wyrażonko zamienia na $k każdy znak należący do grupy (grupy do wyboru zapisujemy w [ ]): gwiazdka (poprzedzona backslashem, bo inaczej sed pomyśli, ze chcemy uzyskać wieloznacznik), nawiasy trójkątne, dwukropek, ciapki: pojedynczy i podwójny, cofnięty ukośnik, pałę pionową i znak zapytania.
s="s/[\*<>:'\"\\\|\?]/$k/g"
Warto zwrócić uwagę na obsługę faktu, że pliki ze spacjami psują wszystko - pętla for uzna spację za rozdzielacz i potworzy kilka nowych plików, które nie istnieją. Aby temu zaradzić wystarczy przedefiniować rozdzielacz:
IFS=$'\n'
Główna pętelka skryptu zawiera dwa warunki, któe formalnie optymalizują kod - bo po cozmieniać nazwę pliku na taką samą, zwłaszcza, że wygeneruje się tylko błąd?
Warunek w 7 linii [ -f $nf ]
sprawdza, czy docelowy plik już istnieje. Może się bowiem zdarzyć istnienie plików o nazwach “coś<>coś” i “coś*>coś”. Wówcza po pierwszej podmiance mamy “coś__coś” i “coś*>”. Żeby uniknąć nadpisać, czy konfliktów tworzony jest dopisek, żeby pliki miały w miarę oryginalne nazwy i się nie gryzły. Zmienna $d o wartości `date +%s%N` to bardzo unikalny dopisek: %s zwraca liczbę sekund od początku epoki (1 stycznia 1970) - bez spacji, ale mało unikalne. Dopisanie %N, czyli nanosekundy wg. zegara procesora tworzy bardzo unikalną nazwę pliku.
for f in `eval ${r}`
do
nf=`echo $f | sed $s`
if [ "$nf" != "$f" ]
#jeśli zmiana nazwy się odbywa (tj. plik ma zmienianą nazwę, a nie tylko jest pomijany)
then
if [ -f $nf ]
then
d=`date +%s%N`
mv -nu "$f" "$nf.$d" 2>/dev/null 1>/dev/null
else
mv -nu $f $nf
fi
echo "$f -> $nf"
fi
done
Cały kod, będący owocem kilku godzin dopieszczania prostego aliasu dla rename poniżej.
#!/bin/bash
if [ -z $1 ]
then
k="_"
elif [ "$1" == "help" ] || [ "$1" == "--help" ] || [ "$1" == "-h" ]
then
echo Jako pierwszy parametr mozesz podac na co zamienic dwukropki
echo Domyslnie jest to podloga \'_\'
echo Drugi parametr gdy przyjmie wartosc minus \'-\' wylaczy rekursje
exit
else
k=$1
fi
#rekursywnie, czy nie
if [ -z $2 ] || [ "$2" != "-" ]
then
r="find . -name '*'"
else [ "$2" == "-" ]
r="find . -maxdepth 1 -name '*'"
fi
#wyrazenie regularne
s="s/[\*<>:'\"\\\|\?]/_/g"
IFS=$'\n' #obsługa plików ze spacjami
for f in `eval ${r}`
do
nf=`echo $f | sed $s`
if [ "$nf" != "$f" ]
#jeśli zmiana nazwy się odbywa (tj. plik ma zmienianą nazwę, a nie tylko jest pomijany)
then
if [ -f $nf ]
#jesli plik docelowy już istnieje bo nazwa rozni sie tylko znakami zakazanymi (ale sa one w tej samej liczbie)
then
d=`date +%s%N` #dosyc unikalny dopisek - licza sekund od epoki+nanosekundy zegara systemowego
mv -nu "$f" "$nf.$d" 2>/dev/null 1>/dev/null
else
mv -nu $f $nf
fi
echo "$f -> $nf"
fi
done
exit 0