Chcę przejrzeć kilka katalogów i zmienić nazwy wszystkich plików kończących się na _test.rb, aby zamiast tego kończyły się na _spec.rb. To jest coś, czego nigdy nie wymyśliłem, jak zrobić z bashem, więc tym razem pomyślałem, że włożyłem trochę wysiłku, aby to przybić. Jednak do tej pory nie wychodzę, mój najlepszy wysiłek to:
find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;
Uwaga: po exec jest dodatkowe echo, więc polecenie jest drukowane zamiast uruchamiania podczas testowania.
Kiedy go uruchomię, dane wyjściowe dla każdej pasującej nazwy pliku to:
mv original original
tj. zamiana seda została utracona. Co to za sztuczka?
Odpowiedzi:
Dzieje się tak, ponieważ
sed
otrzymuje ciąg{}
jako dane wejściowe, co można zweryfikować za pomocą:find . -exec echo `echo "{}" | sed 's/./foo/g'` \;
który wypisuje
foofoo
rekursywnie dla każdego pliku w katalogu. Przyczyną takiego zachowania jest to, że potok jest wykonywany raz przez powłokę, gdy rozszerza całe polecenie.Nie ma możliwości cytowania
sed
potoku w sposób,find
który wykona go dla każdego pliku, ponieważfind
nie wykonuje poleceń przez powłokę i nie ma pojęcia o potokach ani cudzysłowach. Podręcznik GNU findutils wyjaśnia, jak wykonać podobne zadanie, umieszczając potok w osobnym skrypcie powłoki:#!/bin/sh echo "$1" | sed 's/_test.rb$/_spec.rb/'
(Może istnieć jakiś przewrotny sposób użycia
sh -c
i mnóstwo cytatów, aby zrobić to wszystko za pomocą jednego polecenia, ale nie zamierzam próbować).źródło
Aby rozwiązać ten problem w sposób najbardziej zbliżony do pierwotnego, prawdopodobnie użyłbym opcji xargs "args per command line":
find . -name "*_test.rb" | sed -e "p;s/test/spec/" | xargs -n2 mv
Znajduje pliki w bieżącym katalogu roboczym rekurencyjnie, powtarza oryginalną nazwę pliku (
p
), a następnie zmodyfikowaną nazwę (s/test/spec/
) i przekazuje to wszystkomv
parami (xargs -n2
). Uważaj, w tym przypadku sama ścieżka nie powinna zawierać łańcuchatest
.źródło
-z
opcji razem z-print0
wynikami wyszukiwania i xargs-0
:find -name '*._test.rb' -print0 | sed -ze "p;s/test/spec/" | xargs -0 -n2 mv
test
w jednej ścieżce znajduje się wiele folderów.sed
zmieni nazwę tylko pierwszego, amv
polecenie zakończy się niepowodzeniem w przypadkuNo such file or directory
błędu.możesz rozważyć inny sposób, na przykład
for file in $(find . -name "*_test.rb") do echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/` done
źródło
echo $file | sed s/_test.rb$/_spec.rb/
; skończone to jednolinijkowe, prawda?for
podzieli je na osobne słowa. Możesz sprawić, by działało, instruując pętlę for, aby dzieliła się tylko w nowych wierszach. Przykłady można znaleźć pod adresem cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html .-exec
opcji find.Uważam, że ten jest krótszy
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
źródło
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
działa Jak by to byłofind . -name '*_test.rb' -exec bash -c 'echo mv $1 ${1/test.rb/spec.rb}' iAmArgumentZero {} \;
Możesz to zrobić bez seda, jeśli chcesz:
for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done
${var%%suffix}
paskisuffix
od wartościvar
.lub, aby to zrobić używając seda:
for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done
źródło
sed
jedyny), jak wyjaśnia zaakceptowana odpowiedź.for i in... ; do ... ; done
, który wykonuje polecenia przez powłokę i nie rozumie znaku wstecznego .Wspominasz, że używasz
bash
jako powłoki, w takim przypadku tak naprawdę nie potrzebujeszfind
ised
aby uzyskać zmianę nazwy partii, której szukasz ...Zakładając, że używasz
bash
jako powłoki:$ echo $SHELL /bin/bash $ _
... i zakładając, że włączyłeś tzw.
globstar
opcję powłoki:$ shopt -p globstar shopt -s globstar $ _
... i wreszcie zakładając, że zainstalowałeś
rename
narzędzie (znalezione wutil-linux-ng
pakiecie)$ which rename /usr/bin/rename $ _
... wtedy możesz zmienić nazwę partii w linijce bash w następujący sposób:
(
globstar
opcja powłoki zapewni, że bash znajdzie wszystkie pasujące*_test.rb
pliki, bez względu na to, jak głęboko są one zagnieżdżone w hierarchii katalogów ... użyj,help shopt
aby dowiedzieć się, jak ustawić tę opcję)źródło
Najłatwiejszy sposób :
find . -name "*_test.rb" | xargs rename s/_test/_spec/
Najszybszy sposób (zakładając, że masz 4 procesory):
find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/
Jeśli masz dużą liczbę plików do przetworzenia, możliwe jest, że lista nazw plików przesłana potokiem do xargs spowoduje, że wynikowy wiersz poleceń przekroczy maksymalną dozwoloną długość.
Możesz sprawdzić limit swojego systemu za pomocą
getconf ARG_MAX
W większości systemów linuxowych możesz użyć
free -b
lubcat /proc/meminfo
dowiedzieć się, z jaką ilością pamięci RAM musisz pracować; W przeciwnym razie użyjtop
lub aplikacji do monitorowania aktywności systemu.Bezpieczniejszy sposób (zakładając, że masz 1000000 bajtów pamięci RAM do pracy):
find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/
źródło
Oto, co zadziałało, gdy nazwy plików zawierały spacje. Poniższy przykład rekurencyjnie zmienia nazwy wszystkich plików .dar na pliki .zip:
find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;
źródło
Do tego nie potrzebujesz
sed
. Można doskonale się sam na sam zwhile
pętli zasilanej wynikufind
poprzez podstawienie procesowego .Więc jeśli masz
find
wyrażenie, które wybiera potrzebne pliki, użyj składni:while IFS= read -r file; do echo "mv $file ${file%_test.rb}_spec.rb" # remove "echo" when OK! done < <(find -name "*_test.rb")
Spowoduje to
find
pliki i zmianę nazwy wszystkich z nich, usuwając ciąg_test.rb
z końca i dołączając_spec.rb
.W tym kroku używamy rozszerzenia parametrów powłoki, gdzie
${var%string}
usuwa najkrótszy pasujący wzorzec „ciąg” z$var
.$ file="HELLOa_test.rbBYE_test.rb" $ echo "${file%_test.rb}" # remove _test.rb from the end HELLOa_test.rbBYE $ echo "${file%_test.rb}_spec.rb" # remove _test.rb and append _spec.rb HELLOa_test.rbBYE_spec.rb
Zobacz przykład:
$ tree . ├── ab_testArb ├── a_test.rb ├── a_test.rb_test.rb ├── b_test.rb ├── c_test.hello ├── c_test.rb └── mydir └── d_test.rb $ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb") mv ./b_test.rb ./b_spec.rb mv ./mydir/d_test.rb ./mydir/d_spec.rb mv ./a_test.rb ./a_spec.rb mv ./c_test.rb ./c_spec.rb
źródło
while IFS= read -r file; do mv $file ${file%.gz}; done < <(find -type f -name "*.gz")
find .... -exec mv ...
. Uważaj także,$file
ponieważ nie powiedzie się, jeśli zawiera spacje. Lepiej używaj cytatów"$file"
.jeśli masz Ruby (1,9+)
ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'
źródło
W odpowiedzi Ramtam, która mi się podoba, część wyszukiwania działa OK, ale reszta nie, jeśli ścieżka ma spacje. Nie jestem zbyt zaznajomiony z sedem, ale udało mi się zmodyfikować tę odpowiedź na:
find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv
Naprawdę potrzebowałem takiej zmiany, ponieważ w moim przypadku ostateczne polecenie wygląda bardziej jak
find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv
źródło
Nie mam serca, by zrobić to od nowa, ale napisałem to w odpowiedzi na polecenie Commandline Find Sed Exec . Pytający chciał wiedzieć, jak przenieść całe drzewo, być może z wyłączeniem katalogu lub dwóch, i zmienić nazwy wszystkich plików i katalogów zawierających ciąg „STARE”, aby zamiast tego zawierały „NOWY” .
Oprócz opisania poniżej, jak z drobiazgową szczegółowością , ta metoda może być również wyjątkowa, ponieważ zawiera wbudowane debugowanie. Zasadniczo nie robi nic tak, jak napisano, z wyjątkiem kompilowania i zapisywania w zmiennej wszystkich poleceń, które uważa, że powinien wykonać, aby wykonać żądaną pracę.
To również wyraźnie unika pętli tak bardzo, jak to możliwe. Oprócz
sed
rekurencyjnego wyszukiwania więcej niż jednego dopasowania wzorca, o ile wiem, nie ma innej rekursji.I na koniec, jest to całkowicie
null
rozdzielane - nie wyłącza żadnego znaku w żadnej nazwie pliku oprócznull
. Myślę, że nie powinieneś tego mieć.Nawiasem mówiąc, jest to NAPRAWDĘ szybkie. Popatrz:
% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}" > read -r SED <<SED > :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p > SED > find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" | > sort -zg | sed -nz ${SED} | read -r ${6} > echo <<EOF > Prepared commands saved in variable: ${6} > To view do: printf ${6} | tr "\000" "\n" > To run do: sh <<EORUN > $(printf ${6} | tr "\000" "\n") > EORUN > EOF > } % rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}" % time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \ > ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \ > ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \ > | wc - ; echo ${sh_io} | tr "\000" "\n" | tail -n 2 ) <actual process time used:> 0.06s user 0.03s system 106% cpu 0.090 total <output from wc:> Lines Words Bytes 115 362 20691 - <output from tail:> mv .config/replacement_word-chrome-beta/Default/.../googlestars \ .config/replacement_word-chrome-beta/Default/.../replacement_wordstars
UWAGA: Powyższe
function
prawdopodobnie będzie wymagaćGNU
wersjised
ifind
do prawidłowej obsługi wywołańfind printf
ised -z -e
i:;recursive regex test;t
. Jeśli te funkcje nie są dla Ciebie dostępne, funkcja może zostać zduplikowana z kilkoma drobnymi poprawkami.Powinno to zrobić wszystko, co chciałeś od początku do końca bez większego zamieszania. Zrobiłem to
fork
zsed
, ale ćwiczyłem też kilkased
rekurencyjnych technik rozgałęziania, więc dlatego tu jestem. Przypuszczam, że to trochę jak przecena fryzury w szkole fryzjerskiej. Oto przepływ pracy:rm -rf ${UNNECESSARY}
./app
może to być niepożądane. Usuń go lub przenieś wcześniej w inne miejsce lub, alternatywnie, możesz wbudować\( -path PATTERN -exec rm -rf \{\} \)
procedurę,find
aby zrobić to programowo, ale ta jest cała twoja._mvnfind "${@}"
${sh_io}
jest szczególnie ważne, ponieważ zapisuje zwrot z funkcji.${sed_sep}
pojawia się w bliskiej chwili; jest to dowolny ciąg używany do odniesienia się dosed
rekursji w funkcji. Jeśli${sed_sep}
jest ustawione na wartość, która mogłaby potencjalnie zostać znaleziona w dowolnej ścieżce lub nazwie pliku, na której działałeś ... cóż, po prostu nie pozwól, aby tak było.mv -n $1 $2
-noclobber
opcję ustawioną dlamv
; jak napisano, ta funkcja nie umieści${SRC_DIR}
tam, gdzie${TGT_DIR}
już istnieje.read -R SED <<HEREDOC
find . -name ${OLD} -printf
find
proces. W przypadkufind
szukamy tylko wszystkiego, co wymaga zmiany nazwy, ponieważ wykonaliśmy już wszystkiemv
operacje typu miejsce do miejsca za pomocą pierwszego polecenia funkcji. Zamiast wykonywać jakąkolwiek bezpośrednią akcjęfind
, na przykładexec
wywołanie, używamy go do dynamicznego budowania wiersza poleceń z-printf
.%dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
find
zlokalizowaniu plików, których potrzebujemy, bezpośrednio kompiluje i drukuje ( większość ) polecenia, które będziemy musieli przetworzyć na zmianę nazwy.%dir-depth
Dołączona na początku każdej linii przyczyni się do zapewnienia, że nie próbujesz zmienić nazwę pliku lub katalogu w drzewie z obiektu nadrzędnego, który ma dopiero zostać zmieniona.find
używa różnych technik optymalizacji, aby przejść przez drzewo systemu plików i nie jest pewne, że zwróci dane, których potrzebujemy, w kolejności bezpiecznej dla operacji. Dlatego my dalej ...sort -general-numerical -zero-delimited
find
dane wyjściowe w oparciu o%directory-depth
to, aby ścieżki najbliższe relacji do $ {SRC} były przetwarzane jako pierwsze. Pozwala to uniknąć potencjalnych błędów związanych z umieszczaniemmv
plików w nieistniejących lokalizacjach i minimalizuje potrzebę cyklicznego zapętlania. ( w rzeczywistości znalezienie pętli może być trudne )sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
%Path
drukowanym dla każdego ciągu na wypadek, gdyby zawierał więcej niż jedną wartość $ {OLD}, która może wymagać zastąpienia. Wszystkie inne rozwiązania, które sobie wyobrażałem, wymagały drugiegosed
procesu i chociaż krótka pętla może nie być pożądana, z pewnością pokonuje tarło i rozwidlenie całego procesu.sed
tutaj robi, to wyszukanie $ {sed_sep}, a następnie po znalezieniu go, zapisanie go i wszystkich napotkanych znaków, aż znajdzie $ {STARE}, które następnie zastępuje $ {NOWY}. Następnie wraca do $ {sed_sep} i ponownie szuka $ {OLD}, na wypadek gdyby wystąpiło więcej niż raz w ciągu. Jeśli nie zostanie znaleziony, drukuje zmodyfikowany ciągstdout
( do którego następnie przechwytuje ponownie) i kończy pętlę.mv
poleceń, która oczywiście musi zawierać $ {OLD}, zawiera go, a druga połowa jest zmieniana tyle razy, ile jest to konieczne do wyczyszczenia Nazwa $ {OLD} zemv
ścieżki docelowej.sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
-exec
wezwania odbywają się tutaj bez sekundyfork
. W pierwszym, jak widzieliśmy, możemy zmodyfikowaćmv
polecenie dostarczany przezfind
„s-printf
polecenia funkcji jako niezbędne do prawidłowego zmienić wszystkie odniesienia do $ {OLD} do $ {NEW}, ale w tym celu trzeba było korzystać z niektórych dowolne punkty odniesienia, które nie powinny być uwzględniane w ostatecznym wyniku. Więc kiedysed
skończy wszystko, co musi zrobić, poinstruujemy go, aby wymazał punkty odniesienia z bufora wstrzymującego przed przekazaniem go dalej.A TERAZ WRACAMY
read
otrzyma polecenie, które wygląda następująco:% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
Zmieni
read
go w${msg}
taki,${sh_io}
który można dowolnie zbadać poza funkcją.Fajne.
-Mikrofon
źródło
Byłem w stanie obsłużyć nazwy plików ze spacjami, postępując zgodnie z przykładami sugerowanymi przez onitake.
To się nie psuje, jeśli ścieżka zawiera spacje lub ciąg
test
:find . -name "*_test.rb" -print0 | while read -d $'\0' file do echo mv "$file" "$(echo $file | sed s/test/spec/)" done
źródło
To jest przykład, który powinien działać we wszystkich przypadkach. Działa rekursywnie, potrzebuje tylko powłoki i obsługuje nazwy plików ze spacjami.
find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done
źródło
$ find spec -name "*_test.rb" spec/dir2/a_test.rb spec/dir1/a_test.rb $ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);' `spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb' `spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb' $ find spec -name "*_spec.rb" spec/dir2/b_spec.rb spec/dir2/a_spec.rb spec/dir1/a_spec.rb spec/dir1/c_spec.rb
źródło
Twoje pytanie wydaje się dotyczyć seda, ale aby osiągnąć cel polegający na rekurencyjnej zmianie nazwy, proponuję następującą, bezwstydnie wyrwaną z innej odpowiedzi, którą tutaj udzieliłem: rekurencyjna zmiana nazwy w bash
#!/bin/bash IFS=$'\n' function RecurseDirs { for f in "$@" do newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g' echo "${f}" "${newf}" mv "${f}" "${newf}" f="${newf}" if [[ -d "${f}" ]]; then cd "${f}" RecurseDirs $(ls -1 ".") fi done cd .. } RecurseDirs .
źródło
sed
działa bez ucieczki,()
jeśli nie ustawisz-r
opcji?Bezpieczniejszy sposób zmiany nazwy za pomocą funkcji find utils i wyrażenia regularnego sed:
mkdir ~/practice cd ~/practice touch classic.txt.txt touch folk.txt.txt
Usuń rozszerzenie „.txt.txt” w następujący sposób -
cd ~/practice find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;
Jeśli użyjesz + zamiast; aby pracować w trybie wsadowym, powyższe polecenie zmieni nazwę tylko pierwszego pasującego pliku, ale nie całej listy pasujących plików za pomocą polecenia „znajdź”.
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +
źródło
Oto fajny oneliner, który załatwia sprawę. Sed nie może sobie z tym dobrze poradzić, zwłaszcza jeśli wiele zmiennych jest przekazywanych przez xargs z -n 2. Podstawienie bash poradziłoby sobie z tym łatwo, na przykład:
find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'
Dodanie -type -f ograniczy operacje przenoszenia tylko do plików, -print 0 obsłuży puste spacje w ścieżkach.
źródło
Udostępniam ten post, ponieważ jest trochę związany z pytaniem. Przepraszamy, że nie podałem więcej szczegółów. Mam nadzieję, że pomoże to komuś innemu. http://www.peteryu.ca/tutorials/shellscripting/batch_rename
źródło
To jest moje rozwiązanie robocze:
for FILE in {{FILE_PATTERN}}; do echo ${FILE} | mv ${FILE} $(sed 's/{{SOURCE_PATTERN}}/{{TARGET_PATTERN}}/g'); done
źródło