1

Тема: CMD/BAT: Скрипт для массового копирования файлов

Уважаемые гуру!

Прошу помощи в написании bat-скрипта для массового копирования файлов.

Нужно:

- в одной директории находить все файлы, созданные за прошлый месяц;
- перемещать их в отдельную папку;
- в текстовый файл выводить информацию об их количестве и суммарном объёме;
- в другой (или этот же, неважно) файл выводить информацию о количестве и суммарном объёме файлов со строкой «строка 1»;
- в другой (или этот же, неважно) файл выводить информацию о количестве и суммарном объёме файлов со строкой «строка 2»;
- запускать WinRAR на архивацию всех найденных и перемещённых файлов за прошлый месяц с максимальным сжатием;
- удалять исходные файлы после архивации файлы;


Премного благодарен за ответы!

2

Re: CMD/BAT: Скрипт для массового копирования файлов

Похоже на задание.

3

Re: CMD/BAT: Скрипт для массового копирования файлов

Ну, в каком-то смысле любая проблема, требующая решения это задание

Пока вот что удалось сделать самому:


Перемещение всех файлов по дате:

forfiles /M *.txt /D -1 /C "cmd /c move @file c:\123"

Не очень, конечно, изящно, но учитывая что скрипт будет запускаться по первым числам, конструкция /D -1 вполне годна.

Перемещение файлов, содержащих нужную строку:

@echo of
setlocal enabledelayedexpansion
for /f "tokens=*" %%a in ('findstr /M "строка 1" *.txt') do (move "%%a" c:\123)

Опять же не очень изящно перемещать для подсчёта файлы туда-сюда по папкам, но толковее пока среди инструментария bat ничего не нашёл.

Не могу не отметить убогость конструкции оператора /D -1 (указание даты только в сутках) в Windows по сравнению с Linux-оператором date -d "1 month ago". Почувствуйте разницу! А можно написать и date -d "1 year ago" и нет необходимости вычислять количество дней в году и месяце...

4 (изменено: UNDYING, 2013-05-02 13:30:32)

Re: CMD/BAT: Скрипт для массового копирования файлов

Перемещение всех файлов по дате:
forfiles /M *.txt /D -1 /C "cmd /c move @file c:\123"
Не очень, конечно, изящно, но учитывая что скрипт будет запускаться по первым числам, конструкция /D -1 вполне годна.

- в одной директории находить все файлы, созданные за прошлый месяц;

forfiles /?
...
    /D  <дата>          Выбор файлов, у которых дата последнего изменения
                        больше или равна (+) либо меньше или равна
                        (-) указанной дате при использовании формата
                        "dd.MM.yyyy"; либо файлов, у которых дата  последнего
                        изменения больше или равна (+)
                        текущей дате плюс "dd" дней либо меньше или
                        равна (-) текущей дате минус "dd" дней.
                        Допустимым числом дней "dd" является любое
                        число в диапазоне 0 - 32768.
                        "+" используется по умолчанию, если знак не указан.
...

Не могу не отметить убогость конструкции оператора /D -1 (указание даты только в сутках) в Windows по сравнению с Linux-оператором date -d "1 month ago". Почувствуйте разницу! А можно написать и date -d "1 year ago" и нет необходимости вычислять количество дней в году и месяце...

Может тогда GnuWin ?

Если вам подойдёт PowerShell (больше подходит под ваши требования):

- в одной директории находить все файлы, созданные за прошлый месяц;


$a = Get-ChildItem -Recurse -File | where {$_.CreationTime.Month -Like ((Get-Date).Month -1 ) -and $_.CreationTime.Year -Like (Get-Date).Year}

- перемещать их в отдельную папку;

Зачем? - Вы их потом всё равно удалите.

- в текстовый файл выводить информацию об их количестве и суммарном объёме;


$a.Length                # count of objects in array
$a.FullPath | foreach {$s += $_.Length}        # $s = size in bytes

- в другой (или этот же, неважно) файл выводить информацию о количестве и суммарном объёме файлов со строкой «строка 1»;


$fileswithstring1 = Select-String -Path $a.Fullname -Pattern "string1" -List
$fileswithstring1.Lenght    # Count files
$fileswithstring1.FullPath | foreach {$fileswithstring1size += $_.Length}    # Calculate filesizes

- в другой (или этот же, неважно) файл выводить информацию о количестве и суммарном объёме файлов со строкой «строка 2»;


$fileswithstring2 =  Select-String -Path $a.Fullname -Pattern "string2" -List
$fileswithstring2.Lenght    # Count files
$fileswithstring2.FullPath | foreach {$fileswithstring2size += $_.Length}

- запускать WinRAR на архивацию всех найденных и перемещённых файлов за прошлый месяц с максимальным сжатием;


$a.FullPath > "Path_to_file"
# не являюсь знатоком опций WinRar, должно быть как-то так:
winrar a -m9 -Include "Path_to_file" 123.rar

- удалять исходные файлы после архивации файлы;


$a | Remove-Item

Если же PowerShell не подходит, далее возможны варианты:

скрипт будет запускаться по первым числам

forfiles /d +01.04.2013

Более правильный вариант для запуска скрипта 01.05.2013, как работать с датой - посмотрите в "коллекции".
Размер файла:

for %a in ("file_path_and_name") do (echo %~za)

или через dir.
Подсчёт кол-ва - через for, повсеместно встречается на форуме.
Вообще получится довольно-таки медленное и монструозное решение, если хотите пойти таким путём попытайтесь разбить каждый пункт "хотелок" на минимальные (атомарные) операции.

Ещё возможно решение средствами winrar - он умеет архивировать файлы по дате. Подойдёт, если размер данных небольшой.

5

Re: CMD/BAT: Скрипт для массового копирования файлов

«Get-ChildItem -Recurse -File»: «-File» — это в третьей версии такое появилось?

6 (изменено: alexii, 2013-05-02 15:37:08)

Re: CMD/BAT: Скрипт для массового копирования файлов

+ На правах оффтопика…

Опять же не очень изящно перемещать для подсчёта файлы туда-сюда по папкам, но толковее пока среди инструментария bat ничего не нашёл.

Не могу не отметить убогость конструкции оператора /D -1 (указание даты только в сутках) в Windows по сравнению с Linux-оператором date -d "1 month ago". Почувствуйте разницу! А можно написать и date -d "1 year ago" и нет необходимости вычислять количество дней в году и месяце...

Так никто не запрещает Вам для решения задачи использовать Ваш любимый дистрибутив GNU\Linux, *BSD, Solaris, AIX, Irix и т.п. Также как никто Вас не ущемлял в выборе инструмента - sh (bash/zsh/tcsh/прочие), perl, python, Java, C/CPP и прочие.
Нисколько не хочу разжигать "холивар" и беспочвенные дискуссии, но тем кому необходимо "почуствовать" мощь, врядли будут её искать в узкоспециализированном разделе форума.

«Get-ChildItem -Recurse -File»: «-File» — это в третьей версии такое появилось?

Видимо да, в блоге у некто "Kazun"  было описание - "как новая возможность в v3".

7

Re: CMD/BAT: Скрипт для массового копирования файлов

UNDYING, спасибо, ясно.

Условие «$_.CreationTime.Month -Like ((Get-Date).Month -1 ) -and $_.CreationTime.Year -Like (Get-Date).Year» не гоже: не сработает в январе, а в феврале может выдавать лишнее.

Как-то так:

$dtPrevMonthDate = (Get-Date).AddMonths(-1)

$iFromDate = Get-Date -Year $dtPrevMonthDate.Year -Month $dtPrevMonthDate.Month -Day 1
$iToDate   = Get-Date -Year $dtPrevMonthDate.Year -Month $dtPrevMonthDate.Month -Day ([System.DateTime]::DaysInMonth($dtPrevMonthDate.Year, $dtPrevMonthDate.Month))

Get-ChildItem -Path "E:\Песочница\0244\Source\*.txt" -Recurse |`
    Where-Object {!$_.PSIsContainer} |`
        Where-Object {$_.CreationTime -ge $iFromDate -and $_.CreationTime -le $iToDate} 

или так:

$sPrevMonthDate = ((Get-Date).AddMonths(-1)).ToString("MM.yyyy")

Get-ChildItem -Path "E:\Песочница\0244\Source\*.txt" -Recurse |`
    Where-Object {!$_.PSIsContainer} |`
        Where-Object {($_.CreationTime).ToString("MM.yyyy") -eq $sPrevMonthDate} 

Второй вариант короче, но первый — нагляднее.

8 (изменено: UNDYING, 2013-05-02 17:21:57)

Re: CMD/BAT: Скрипт для массового копирования файлов

alexii пишет:

а в феврале может выдавать лишнее.

Ну насчёт января ясно, а в феврале что за "грабли". Другими словами - как это выражается (можно ссылку и т.п.)?

Get-ChildItem -Path "E:\Песочница\0244\Source\*.txt" -Recurse ...

Если память не изменяет, есть параметры -Filter и -Include - "нагляднее" маску складывать туда, а "путь оставить путём". (Правда это выглядит уже как нападки с моей стороны )


$sPrevMonthDate = ((Get-Date).AddMonths(-1)).ToString("MM.yyyy")

Get-ChildItem -Path "E:\Песочница\0244\Source\*.txt" -Recurse |`
    Where-Object {!$_.PSIsContainer} |`
        Where-Object {($_.CreationTime).ToString("MM.yyyy") -eq $sPrevMonthDate} 

По сути верно "вогнать" в $sPrevMonthDate месяц для сравнения и не заниматься этим для каждого вызова Where-Object, но зато каждый раз вызывается метод объекта и сравниваются строки. Скажем так, надо будет посмотреть как "поизящней" воплотить сравнение дат. Для объектного языка это критично, т.к. теряется основной смысл объектов.
И у PoSh есть существенный недостаток (во всяком случае для меня) - сложно рассматривать конструкции "в уме" или "на бумаге" - обязательно надо пробовать - что оно такое и как оно себя ведёт вообще.

Как-то так, без преобразования даты в строку:


$sPrevMonthDate = (Get-Date).AddMonths(-1)

Get-ChildItem -Path "E:\Песочница\0244\Source" -Filter *.txt -Recurse |`
    Where-Object {!$_.PSIsContainer} |`
        Where-Object {($_.CreationTime).Month -eq $sPrevMonthDate.Month -and ($_.CreationTime).Year -eq $sPrevMonthDate.Year} 

И ещё:

Второй вариант короче, но первый — нагляднее.

Мне так не кажется - виноваты конструкты типа:


... -Day ([System.DateTime]::DaysInMonth($dtPrevMonthDate.Year, $dtPrevMonthDate.Month))

Дольше времени требуется на анализ "а что-таки автор имел в виду на самом деле?". И я бы сказал для "детского скрипта", каким он, всё-таки, является - это "извращение".
Хотя, "оффтоп" я тут развёл, поэтому меня не покидает мысль - а не пора ли создать раздел для PoSh?

9

Re: CMD/BAT: Скрипт для массового копирования файлов

а в феврале что за "грабли". Другими словами - как это выражается (можно ссылку и т.п.)?

Был не прав. Неверно интерпретировал работу оператора «-like». Вопрос, а почему не «-eq» там было, почему именно «-like»?

Как-то так, без преобразования даты в строку…

Превосходно! Намного нагляднее. Глядишь, так и научусь. Когда-нибудь .

И я бы сказал для "детского скрипта", каким он, всё-таки, является - это "извращение".

Не догадался до более изящного решения, приведённого Вами. Что нашёл из связки PowerShell'а и .Net, то и пробовал. А нагляднее — потому как на мой взгляд сравнение дат даёт большую наглядность, нежели сравнение строк.

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

а не пора ли создать раздел для PoSh?

+ «Их у нас был»

Самоликвидировался по причине почти полного отсутствия активности в нём. Там, почитай, один коллега greg zakharov и был. Раздел в Коллекции остался. Завести, конечно, можно, но пока не будет массового перехода корпоративных клиентов на Windows 7 (с будущего года, полагаю), рассчитывать на увеличение активности не стоит. Соотношение CMD/WSH/PoSH (здесь) — примерно как 100/70/1. Пусть пока будут в Прочие скриптовые технологии и близкие к ним. Будет заметная и постоянная активность — собрать темы в отдельный раздел труда не составит.

10

Re: CMD/BAT: Скрипт для массового копирования файлов

alexii пишет:

Был не прав. Неверно интерпретировал работу оператора «-like». Вопрос, а почему не «-eq» там было, почему именно «-like»?

В данном случае уместнее -eq, -like "утащил" из кода над которым работаю сейчас.
Согласно документации без использования знаков подстанови ("*","?", "[", "]" и т.п.) полностью аналогичен -eq (но не -ceq - Case sensitive EQ). Точнее, что-то среднее между -Match и -Eq .

Превосходно! Намного нагляднее. Глядишь, так и научусь. Когда-нибудь .

Это явно перебор , там лишь пара исправлений.
Вообще, если размышлять о "красивости", необходимо каким-то образом избавиься от конструкции (но таким образом теряем совместимость с v2):


...
Where-Object {!$_.PSIsContainer} |
...

и двойного сравнения через -and (ума не приложу как, и сам хочу понять, как сравнить строку с массивом строк - может быть поменять их местами?), а в идеале и от Get-ChildItem -Recurse - т.к. "знающие люди" говорят про низкую скорость работы (требует проверки - насколько? во сколько раз?).

Не догадался до более изящного решения, приведённого Вами. Что нашёл из связки PowerShell'а и .Net, то и пробовал. А нагляднее — потому как на мой взгляд сравнение дат даёт большую наглядность, нежели сравнение строк.

  1. Использование "inline dotNet" - это, конечно, фича, но не вижу смысла использовать её везде, где только можно. Логично предположить - там где "стандартное" решение невозможно и нет альтернатив, или стандартное решение и/или альтернативы "убоги" для данной задачи.

  2. Зачем сравнивать по дням и мучаться с вычислением дней в месяце когда в условии чёткая формулировка "за 1 конкретный месяц" (операционки нынче умные и создать файл с датой 30.02.2013 попросту не смогут)?

В данном направлении есть интересная заметка у Vadims Pod?ns:

+ Алиасы

Алиасы, here strings и несколько практических советов при работе с текстом
Семантика языка PowerShell подразумевает использование унифицированного синтаксиса, где название каждой команды явно говорит о том, что она делает. Например, Get-Process. Совершенно очевидно, что эта команда должна делать. Но, порой, эти команды бывают очень длинными и набирать их постоянно в консоли бывает не очень удобно. Например, самый топовый — Get-ChildItem. Это даже не самая длинная команда, просто наиболее часто используемая. Или Foreach-Object. Даже автозавершение команд не всегда спасает ситуацию. Для этого были придуманы алиасы (короткие ссылки на команды), которые очень выгодно использовать в консоли. Так же у команд есть и очень длинные параметры. Например, всякие –InputObject, –Include, –ErrorAction и т.д. PowerShell позволяет сокращать параметры первыми буквами до тех пор, пока эти буквы не будут явно указывать на конкретное название. Например, у команды Get-ChildItem параметр –Include может быть сокращён до –I, а –Exclude до –Ex. Но многие скриптописатели пишут скрипты (и выкладывают их даже где-то) с использованием этих самых алиасов и коротких обозначений параметров.

Хорошо это или плохо? Ответ очевидный — за такое надо бить больно сапогами и по лицу. Использование алиасов приводит нас обратно к одной из проблем оболочки cmd — сразу не скажешь, что делает та или иная команда. Пользователь без соответствующей подготовки вряд ли сходу скажет, что делает команда regsvr32 или что делает ключ /i этой команды. Или вот 2 примера:

    gps iex* | spps -f
    ls .\ -r -fo | %{cp $_.fullname -des e:\ -ea 0}

Вы можете такое использовать в консоли, но не в скриптах. В скриптах эти две строчки должны выглядить только вот так:

    Get-Process iex* | Stop-Process -Force
    Get-ChildItem .\ -Recurse -Force | ForEach-Object {Copy-Item $_.fullname -Destination e:\ -ErrorAction SilentlyContinue}

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

Пользователи PowerGUI Script Editor могут воспользовться адд-оном, написанным одним пошикмвп Шейем Леви (Shay Levy) — Expand Alias, который автоматически разворачивает алиасы в их полное значение.

Также многие моменты/вещи можно "утянуть"/"подсмотреть" в блоге, например PS_FCIV, который, правда, требует допила, для того чтобы быть внешне совместимым с оригинальным fciv.exe (есть проблемы вида - "не умею обрабатывать одиночный файл, к примеру - вычислить его MD5" и "вырвиглазый вывод на консоль").

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

Чтож, от этого не убежишь, "все люди ошибаются, т.к. людям свойственно ошибаться". И всё требует знаний, умений и практики.
Быть "универсалом" страшная вешь - несмотря на горы знаний, "большим" специалистом всё равно не стать, посему лучше избрать свою, довольно узкую стезю и работать в её пределах, отвлекаясь лишь на поверхностное изучение как смежных, так и далёких областей.
К примеру, у меня стойкая антипатия к *Basic*, потому что "он, по обыкновению, нигде, никому не нужен" (личная "фобия" , хотя как язык для обучения, наверное, один из самых лучших).

«Их у нас был»
...
Завести, конечно, можно, но пока не будет массового перехода корпоративных клиентов на Windows 7 (с будущего года, полагаю), рассчитывать на увеличение активности не стоит.
...

Нет проблем, "... нет ребята, я не гордый, я согласен на медаль ...".
Качество и т.н. "нужность/необходимость" кода в коллекци просто отвратное, но не будем об этом.

11

Re: CMD/BAT: Скрипт для массового копирования файлов

Это явно перебор wink, там лишь пара исправлений.

Почему нет? Я вот, например, как-то не сообразил, что можно танцевать и от обратного — получать partial date от «.CreationTime», и начало/конец интервала уже попросту не понадобится получать.

Использование "inline dotNet" - это, конечно, фича, но не вижу смысла использовать её везде, где только можно. Логично предположить - там где "стандартное" решение невозможно и нет альтернатив, или стандартное решение и/или альтернативы "убоги" для данной задачи.

Беда в том, что идея привязки PowerShell'а к .Net привела к возможности существования более чем единственного варианта решения. Плюс отвратительно организованная справка (я не знаю, как обстоят дела в третьей версии, но в предыдущих — просто ужас).

Быть "универсалом" страшная вешь - несмотря на горы знаний, "большим" специалистом всё равно не стать, посему лучше избрать свою, довольно узкую стезю и работать в её пределах, отвлекаясь лишь на поверхностное изучение как смежных, так и далёких областей. sad

Это не от тебя зависит: если работа требует узкой специализации — будешь узким специалом, если работа требует Александра Матросова — будешь и им. У варианта узкого специалиста есть один большой минус — помните, да? «Его полнота односторонняя». Учитывая то, что в настоящее время в нашей отрасли не найти несвязанных между собой технологий — лучше быть универсалом со специализацией в каких-то приоритетных областях.

К примеру, у меня стойкая антипатия к *Basic*, потому что "он, по обыкновению, нигде, никому не нужен" (личная "фобия" wink, хотя как язык для обучения, наверное, один из самых лучших).

Напрасно. Являясь одним из наиболее популярных встраиваемых прикладных языков программирования, Basic даёт наибольший охват продуктов. Для обучения же, на мой непросвещённый взгляд, лучше пользовать стандартный Паскаль.

Качество и т.н. "нужность/необходимость" кода в коллекци просто отвратное, но не будем об этом.

Это извечная проблема: кто будет решать, что достойно, а что — нет.

P.S. Насколько я знаю, по сю пору нет ни одного вменяемого учебника по PoSH на русском языке. Впрочем, по WSH тоже не было.