Menu Zamknij

Szybka podmiana zakazanych znaków pod NTFS

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

Dodaj komentarz