2017-01-04 5 views
1

Из текущего каталога у меня есть несколько вложенных каталоги:Идите в каждый подкаталог и массовое переименование файлов отгонки ведущих символов

subdir1/ 
001myfile001A.txt 
002myfile002A.txt 
subdir2/ 
001myfile001B.txt 
002myfile002B.txt 

, где я хочу раздеться каждый символ из имени файла, прежде чем myfile, так что я в конечном итоге с

subdir1/ 
myfile001A.txt 
myfile002A.txt 
subdir2/ 
myfile001B.txt 
myfile002B.txt 

у меня есть некоторый код, чтобы сделать это ...

#!/bin/bash 
for d in `find . -type d -maxdepth 1`; do 
    cd "$d" 
    for f in `find . "*.txt"`; do 
     mv "$f" "$(echo "$f" | sed -r 's/^.*myfile/myfile/')" 
    done 

done 

однако вновь переименованы файлы в конечном итоге в родительском каталоге

т.е.

myfile001A.txt 
myfile002A.txt 
myfile001B.txt 
myfile002B.txt 
subdir1/ 
subdir2/ 

В которой подкаталоги теперь пусто.

Как изменить свой сценарий, чтобы переименовать файлы и сохранить их в своих соответствующих подкаталогах? Как вы можете видеть, что первый цикл изменения каталога в подкаталог поэтому не знаю, почему файлы в конечном итоге получить вознес каталог ...

ответ

1

У вашего сценария много проблем. Во-первых, ваша внешняя команда find не делает то, что вы ожидаете: она выводит не только каждый из подкаталогов, но и корень поиска, ., который сам является каталогом. Вы могли бы обнаружить это, выполнив команду вручную, среди прочего. Вам не нужно использовать find для этого, но предположим, что вы сделать использовать его, это было бы лучше:

for d in $(find * -maxdepth 0 -type d); do 

Кроме того, . является первый результат оригинального find команды, и ваши проблемы продолжаются. Ваш начальный cd не имеет значимого эффекта, потому что вы просто переходите в тот же каталог, в котором вы уже находитесь. Команда find во внутреннем цикле коренится там и спускается в оба подкаталога. Поэтому информация о пути для каждого файла, который вы выбираете для переименования, разделяется sed, поэтому результаты заканчиваются в начальном рабочем каталоге (./subdir1/001myfile001A.txt ->myfile001A.txt). К тому времени, когда вы обрабатываете подкаталоги, в них нет файлов, которые можно переименовать.

Но это еще не все: команда find неверна в вашей внутренней петле. Поскольку вы не указываете опцию перед этим, find интерпретирует "*.txt" как обозначение второго корня поиска, в дополнение к .. Предположительно вы хотели использовать -name "*.txt" для фильтрации результатов поиска; без него find выводит имя каждого файла в дереве. Предположительно, вы подавляете или игнорируете полученные сообщения об ошибках.

Но если предположить, что ваши подпапки не имеют подкаталоги своей собственной, как показано на рисунке, и что вы не связаны с, даже составляют скрытые этой исправленной версии ...

for f in `find . -name "*.txt"`; 

... это ужасно тяжеловес способ сказать это ...

for f in *.txt; 

... или даже это ...

for f in *?myfile*.txt; 

... последний из которых будет избегать попыток переименовать любые файлы, имена которых фактически не меняются.

Кроме того, запуск sed процесса для каждого имени файла довольно расточительно и дорого, когда вы могли бы просто использовать bash's встроенные подстановки функции:

mv "$f" "${f/#*myfile/myfile}" 

И вы также обнаружите, что ваш рабочий каталог испортится , Рабочий каталог является характеристикой общей среды оболочки, поэтому он не автоматически сбрасывается на каждой итерации цикла. Вам нужно будет каким-то образом обработать это вручную. pushd/popd сделал бы это, как и тело внешнего цикла в подоболочке.

В целом, это будет делать трюк:

#!/bin/bash 

for d in $(find * -maxdepth 0 -type d); do 
    pushd "$d" 
    for f in *.txt; do 
     mv "$f" "${f/#*myfile/myfile}" 
    done 
    popd 
done 
+1

я был испорчен для выбора правильных ответов, но этот был так тщательно и указал, что я делаю неправильно, а также некоторые лучшие практики. Благодаря! – brucezepplin

1

Вы можете сделать это без find и СЭД:

$ for f in */*.txt; do echo mv "$f" "${f/\/*myfile/\/myfile}"; done 
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt 
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt 
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt 
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt 

Если вы удалите echo, он фактически переименует файлы.

Используется shell parameter expansion для замены косой черты и всего до myfile с косой чертой и myfile.

Обратите внимание, что это происходит, если имеется более одного уровня подкаталогов. В этом случае, вы можете использовать расширенный pattern matching (включен с shopt -s extglob) и вариант globstar оболочки (shopt -s globstar):

$ for f in **/*.txt; do echo mv "$f" "${f/\/*([!\/])myfile/\/myfile}"; done 
mv subdir1/001myfile001A.txt subdir1/myfile001A.txt 
mv subdir1/002myfile002A.txt subdir1/myfile002A.txt 
mv subdir1/subdir3/001myfile001A.txt subdir1/subdir3/myfile001A.txt 
mv subdir1/subdir3/002myfile002A.txt subdir1/subdir3/myfile002A.txt 
mv subdir2/001myfile001B.txt subdir2/myfile001B.txt 
mv subdir2/002myfile002B.txt subdir2/myfile002B.txt 

Это использует *([!\/]) шаблон («ноль или более символов, которые не являются прямой слэш»). Косую черту следует избегать в выражении скобки, потому что мы все еще внутри pattern часть расширения ${parameter/pattern/string}.

1

небольшое изменение должно исправить вашу проблему:

#!/bin/bash 
for f in `find . -maxdepth 2 -name "*.txt"`; do 
    mv "$f" "$(echo "$f" | sed -r 's,[^/]+(myfile),\1,')" 
done 

примечание: sed использует , вместо / в качестве разделителя.

Однако есть гораздо более быстрые способы.

здесь с помощью утилиты переименования, доступны или легко установить везде, где есть bash и perl:

find . -maxdepth 2 -name "*.txt" | rename 's,[^/]+(myfile),/$1,' 

здесь тесты на 1000 файлов:

for `find`; do mv  9.176s 
rename     0.099s 

Это 100x так быстро.принял ответ

Джона Боллинджера в два раза быстрее, чем в ФОС, но 50x так медленно, как это rename решение:

for|for|mv "$f" "${f//}" 4.316s 

также, он не будет работать, если есть каталог с слишком много элементов на shell glob. также любые ответы, которые используют for f in *.txt или for f in */*.txt или find * или rename ... subdir*/*. ответы, которые начинаются с find ., с другой стороны, будут также работать с каталогами с любым количеством элементов.

1

Может быть, вы хотите использовать следующую команду вместо:

rename 's#(.*/).*(myfile.*)#$1$2#' subdir*/* 

Вы можете использовать rename -n ... чтобы проверить результат без фактического переименования ничего.

Что касается вашего актуального вопроса:
Команды находки из внешнего цикла возвращает 3 директории (!):

. 
./subdir1 
./subdir2 

Нежелательный . является причиной, почему все файлы в конечном итоге в родительском каталоге (то есть .). Вы можете исключить ., используя опцию -mindepth 1. К сожалению, это была причина, по которой файлы приземляются не в том месте, но не единственная проблема. Поскольку вы уже приняли один из ответов, нет необходимости перечислить их все.