Сортировка изображений по разрешению… на сцене PowerShell

В очередной раз просматривая интересности, любопытности и прочие всякости на Хабре, натолкнулся на статью о том, как с помощью Питона навести порядок среди иллюстраций, цифровое кладбище которых имеется почти у каждого из нас. Поскольку не так давно мне выпала доля заниматься процессингом картинок с использованием PowerShell, я решил провести показательное сравнение. Показательное с той точки зрения, чтобы продемонстрировать некоторые характерные возможности PowerShell тем, кто еще пока не знаком с ним или знаком поверхностно.

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

Начну с небольшого лирического отступления. Даже когда говоришь о таких, вроде бы простых инструментах, как командные процессоры, хочется чего-то возвышенного. И мне кажется, я его нашел. Вы будете смеяться, но я определил для себя разработку под командные процессоры как мультипарадигменную. Первая парадигма — императивная. Мы видим ее практически во всех командных файлах и видели ее в примере исходной задачи на Питоне. Другая парадигма — функциональная. Я ее так назвал в силу ее схожести с подходом, используемым в функциональных языках программирования. В быту же мы ее знаем как командные пайпы, просто пайпы и много других ласковых терминов :) Вкратце напомню, как это выглядит, на простом примере:

X:\> (dir /b folder1\*.txt && dir /b folder2\*.txt) | find "text" | sort

Здесь мы видим три инструкции, разделенных вертикальной чертой. Каждая последующая берет результаты выполнения предыдущей, выполняет над ними определенные операции и передает следующей команде в очереди пайпа. В нашем примере стандартный cmd.exe собирает при помощи первой инструкции список текстовых файлов из двух папок. Этот список передается команде find, которая оставляет только те строки, которые содержат подстроку «text» и уже они отправляются команде sort, которая их сортирует. В функциональных языках программирования это могло бы выглядеть так:

sort(find((dir /b folder1\*.txt && dir /b folder2\*.txt), "text"))

Не правда ли, есть определенные сходства? По сути, каждый элемент пайпа сродни функции. Просто к существующим типам записей функций (как то постфиксная, префиксная и инфиксная записи) добавилась еще одна — пайповая запись :)

Не смотря на то, что командные пайпы, как видно из примера, были еще со времен MS-DOS, я бы хотел отдельно поблагодарить UNIX-сообщество, что звучит странно, учитывая происхождение PowerShell в горнилах Microsoft. Но тому есть простое объяснение. Именно в UNIX-подобных системах данные механизмы были возведены в ранг искусства, позволяя объединять различные команды в самые необычные и весьма полезные комбинации.

Так получилось, что PowerShell почерпнул именно эту, на мой взгляд очень удачную черту и объединил с другим, не менее удачным решением. В отличие от передачи строк в командных пайпах того же Linux, PowerShell оперирует объектами. Чтобы все стало понятней, предлагаю приступить к реализации поставленной задачи. Делать мы это будем, разумеется, с использованием «функционального» подхода через пайпы, ибо императивный подход отличался бы мало от того, что мы уже имеем в случае с реализацией на Питоне. А хочется сравнить не только инструменты, но и парадигмы.

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

Шаг 0. Для начала мы опишем некоторые условия исполнения в целом. Во-первых — нас интересуют входные параметры и это хороший повод рассмотреть работу с переменными в PowerShell.

PS X:\> $source="x:\folder\source"
PS X:\> $target="x:\folder\target"
PS X:\> $source, $target
x:\folder\source
x:\folder\target


В первой строке переменной $source мы определили в качестве значения исходную папку, в которой расположены картинки для последующей сортировки. В папке назначения $target мы разместим отсортированные картинки. Третья строка просто говорит о том, что надо вывести значения этих переменных на консоль, что мы и видим далее. Заметьте, что значения не просто так взяты в кавычки. Дело в том, что значения переменных типизированы и таким образом мы определили их как строки. В отсутствие кавычек процессор будет рассматривать текст как команду и присвоит переменной результаты ее исполнения. Например:

PS X:\> $test=dir x:\folder\source
PS X:\> $test.Length

10

PS X:\> $test[0].GetType()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True DirectoryInfo System.IO.FileSystemInfo


В результате такой команды переменная $test будет коллекцией объектов в заданной папке. Свойство Length, которые мы использовали в инструкции $test.Length — это количество элементов в коллекции. А вот $test[0].GetType() выводит информацию о типе первого элемента коллекции. Как видите, это не простая строка, а некий DirectoryInfo. Будь первым элементов файл — был бы FileInfo. Это очень важная иллюстрация к тому, что я говорил ранее и тому, что мы будем активно использовать позднее — PowerShell передает по пайпу не строки, а объекты вполне определенных типов.

Следующий подготовительный шаг связан с тем, что мы будем использовать тип иллюстрации, который размещен в библиотеке System.Windows.Forms, не загружаемой по умолчанию. Нам необходимо дать инструкцию PowerShell, чтобы он ее загрузил. Например, так:

PS X:\> [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")

Вообще здесь проглядывается еще одна существенная особенность PowerShell — это создание любых объектов, которые предлагает .NET или COM. Имя им легион, но это уже отдельная тема. В данном случае просто примем данную строку как данность. За сим будем считать, что среда исполнения готова.

Шаг 1. После того, как условия исполнения задачи готовы, мы начнем с формирования списка файлов, которые претендуют на то, чтобы быть картинкам. Формулировка задачи именно такова, ибо мы исходим из предположения, что расширение файла — это полезная информация, но не гарантирующая того, что файл является картинкой. В большинстве случаев вам это не понадобиться, но как я говорил ранее — решение намеренно усложнено. Итак, мы выбираем все файлы заданных расширений в заданной папке и всех ее подпапках. Выглядеть наш первый шаг пайпа будет так:

PS X:\> dir $source -r -include *.jpg, *.png, *.gif

Опция "-r" означает рекурсивный обход директорий, в "-include" вы можете перечислить маски включаемых файлов (либо перечислить макси исключаемых в опции "-exclude"). В ответ на эту команду мы получим список файлов.

Шаг 2. Следующим элементом пайпа мы пытаемся создать для каждого файла, полученного от предыдущей инструкции, объект растровой картинки. Данная инструкция служит иллюстрацией сразу нескольким возможностям PowerShell, но начнем, для ясности картины, с примера:

PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue

Первое, что мы видим во второй инструкции пайпа — это команду select. Ее назначение состоит в том, чтобы сформировать новые объекты и передать их дальше. Для этого в select через запятую перечисляются все свойства нового объекта, которые нас интересуют. Первым идет FullName. Указанное в таком виде свойство означает, что мы берем его из объекта, доставшегося нам по пайпу и с тем же именем и значением передаем новому объекту. В нашем случае речь идет о свойстве FullName класса FileInfo, которое возвращает полный путь к файлу.

Следующая конструкция немного сложнее. Она создает новое свойство, имя которого передается в Name, а значение в Expression. В качестве значения мы создаем экземпляр класса, описывающего иллюстрацию (System.Drawing.Bitmap), передавая его конструктору тоже самое значение FullName с расположением файла иллюстрации. Отдельно отметьте для себя разницу в синтаксисе обращения к свойству FullName. Инструкция select делает это в упрощенном виде. В большинстве же остальных случаев переменная $_ означает объект, переданный нам по пайпу, к свойству которого мы можем обратиться через точку и имя свойства.

Если файл, с которым мы собираемся работать не является растровой иллюстрацией, то попытка создания объекта System.Drawing.Bitmap привела бы к ошибке. Для того, чтобы эти ошибки проигнорировать мы добавили опцию ErrorAction, которая позволяет их проигнорировать. Отметьте для себя, что эта опция не является уникальной для команды select, а относится к разряду так называемых Common Parameters, которые вы можете использовать практически в любых других инструкциях.

Шаг 3. По итогам предыдущего шага мы получим список объектов, каждый о двух свойствах: FullName с полным путем к имени файла и Image с иллюстрацией в виде экземпляра класса Bitmap. Если для какого-либо из файлов не удалось создать класс растровой картинки, то свойство Image будет пустым. Значит нам нужен шаг, который позволить отфильтровать все объекты, которые не являются иллюстрациями. Итог дополнения новой инструкцией будет такой:

PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue | where { $_.Image }

Все достаточно просто и лаконично. Мы встречаем знакомое нам обращение к свойству. В данном случае к свойству Image. Встречаем новую инструкцию where, которая позволяет передать дальше по пайпу только те объекты, которые удовлетворяют заданному условию в ней условию. Заодно знакомимся с простой проверкой на пустые значения. Непустое мы бы контролировали условием !$_.Image, а к более сложным условиям бы привлекали операции сравнения, логические операции и т.п. Например — where {$_.Image.Width -gt 1000 -and $_.Image.Height -gt 1000} для получения всех иллюстраций чьи ширина и высота больше 1000.

Шаг 4. После того, как мы получили перечень иллюстраций мы сформируем для каждой из них имя папки, в которой иллюстрация должна быть сохранена. Сделаем мы это так:

PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue | where { $_.Image } | select FullName, @{Name="ImageFolder"; Expression={"{0}\{1}x{2}" -f $target, $_.Image.Width, $_.Image.Height}}

С командой select и формирование нового объекта вы уже знакомы, больший интерес здесь представляет форматирование строк. Как видно из примера, начинается все со строки с форматом, а затем перечисляются значения, которые будут использованы при форматировании. Все в большей мере соответствует методу string.Format из .NET и с правилами форматирования можно ознакомиться в MSDN.

Шаг 5. Посмотрев на результат исполнения этой функции вы увидите, что уже есть практически все, что нам нужно. А именно — мы имеем полный путь к исходной иллюстрации все в том же свойстве FullName и путь назначения с папками согласно размерам в новом свойстве ImageFolder. Остался сплошной императив по созданию папки и копированию/перемещению туда файла. Для этого мы воспользуемся инструкцией foreach, которая позволяет выполнить другие инструкции для каждого объекта, полученного в пайпе. Выглядеть все вместе это будет так:

PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue | where { $_.Image } | select FullName, @{Name="ImageFolder"; Expression={"{0}\{1}x{2}" -f $target, $_.Image.Width, $_.Image.Height}} | foreach {if (-not (test-path $_.ImageFolder)) {md $_.ImageFolder}; copy $_.FullName -destination $_.ImageFolder; $_}

Как видите, в foreach разместились три инструкции, разделенные точкой с запятой. Вторая определенно не нуждается в каких-либо пространных комментариях, ибо представляет из себя простое копирование. Которое, к слову, может быть заменено на команду move для перемещения файла иллюстрации. Первая инструкция чуть длиннее, но не намного сложнее. В условной конструкции if мы проверяем отсутствие папки при помощи логического отрицания и конструкции test-path. Если папка отсутствует, то только в таком случае мы ее создаем. Третьей инструкции можно было вовсе избежать, но ей я хотел показать, что foreach не является терминальной инструкцией в пайпе и после нее обработка может быть продолжена. Помните, как на самом первом шаге мы выводили значения переменных на консоль? Так и здесь, инструкция $_ выводит объект, который мы получили по пайпу дальше в пайп. Вместо него вы можете вывести что-либо другое. Например, определить какую-нибудь переменную и вывести ее, скажем foreach {….; $result = …; $result}.

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

UPD: Огромное спасибо amirul за пример в императивном стиле, который я сделать поленился. Надеюсь это снимет некоторые проблемы с читаемостью кода. Хотя, не скрою, хочется чтобы был понят и функциональный подход. Он ничуть не сложнее, ведь замысловатая с виду строка проходит элементарную декомпозицию на примитивные атомы. Просто это непривычно, как непривычен многим из нас синтаксис LISP, например.

Copy Source | Copy HTML
  1. [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
  2.  
  3. $source = "x:\source"
  4. $target = "x:\target"
  5.  
  6. foreach ($file in dir $source -r -inc *.jpg, *.gif, *.png) {
  7.     try {
  8.         $image = new-object System.Drawing.Bitmap $file.FullName
  9.         $targetdir = "{0}\{1}x{2}" -f $target, $image.Width, $image.Height
  10.         if (!(test-path $targetdir)) {
  11.             md $targetdir
  12.         }
  13.         copy $file $targetdir
  14.  
  15.         Write-Host $file -> $targetdir
  16.     } catch {
  17.         Write-Host $file " **IS NOT COPIED**"
  18.     }
  19. }
  20.  
+32
25 февраля 2010, 20:43
25
Guderian 38,5

комментарии (74)

+1
d3ot #
Много раз видел у себя этот PowerShell ну не имел представления для чего он!

З.Ы. Спасибо за статью, довольно познавательно.
+1
mcrey #
Спасибо интересно было почитать :)

З.Ы. Неделя сортировки изображений по разрешению…
–1
jite #
Ага, может быть тогда уже сделать отдельный пост «Сортировка по разрешению — на всех языках»:
— ссылки на различные реализации
— еще не задействованные ЯП и библиотеки
— сравнение по скорости
–1
jite #
Интересно, это все-таки шутка или предложение было?
0
Guderian #
В каждой шутке есть доля предложения :) На самом деле я считаю Ваш способ наиболее удачным для объективного сравнения языков. Ибо обычно их сравнительный анализ сводится к демагогии сторон. А будь где-нибудь, когда-нибудь фолиант с большим количеством примеров на самых разных языках и с результатами сравнения этих примеров по некоторым объективным показателям (скорость, размер кода и т.п.), у нас был бы повод адекватно посмотреть на возможности, не опираясь на собственные привычки.
+1
ddsl #
А я всего лишь хотел 7апа и инвайт на хабр о_О
0
silentroach #
Чорт, надо было запостить это сюда :)
–2
vadv #
Одного не понимаю, зачем сие чудо по умолчанию в установку ставить.
Зачем домохозяйке изощрятся в написании скриптов? Разве что расширить возможности зараженного «зомби».
–1
demmsnt #
Затем, что это не для домохозяйки. А скинуть скрипт за 300р и не просить его ставить Python уже можно. Постепенно Win становится дружелюбнее
+2
vadv #
Осталось только SSH добавить, и Win будет совсем дружелюбным.
+3
demmsnt #
Он есть — www.freesshd.com/ Я использую ежедневно. Конечно ощущение, что у велосипеда квадратные колеса, но все-же.
0
Xaegr #
Есть кое что получше — WinRM, включается политиками.
0
Xaegr #
Вообщ в Vista и 7 большая часть нового функционала именно для администраторов, а не для домохозяек. Им то и aero хватит :)
+7
VlK #
>>>dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name=«Image»; Expression={New-Object
>>> System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue | where { $_.Image } | select FullName, >>>@{Name=«ImageFolder»; Expression={"{0}\{1}x{2}" -f $target, $_.Image.Width, $_.Image.Height}} | foreach {if (-not
>>> (test-path $_.ImageFolder)) {md $_.ImageFolder}; copy $_.FullName -destination $_.ImageFolder; $_}

может, все же наглядней было бы на Питоне? :)
–1
Ivanhoe #
Отдельно отмечу, что я буду намеренно немного усложнять реализацию, чтобы показать больше возможностей PowerShell. В реальных условиях что-то можно упростить, а от чего-то вообще избавиться.

0
demmsnt #
Вы не путайте PIL конечно нагляднее, но я рад, что хоть какой-то стандартный Shell появился. Все эти {0} это .NET. Ждем вирусы :-)
+1
Guria #
там скрипты по-умолчанию untrusted вроде и сами не запускается по дабл-клику, надо хорошо попросить
0
demmsnt #
Эт шутк был. RDP вот тоже есть, но все используют VNC/RКто-то-там И это обидно. Хочется чтоб Юзер мог скинуть URL по Jabber и я увидел его экран.

Я так понимаю, надо скрипт запустить типа PWShell script.pws? Или Окошко выскакивает постоянно «Вы нажали на кнопку?»
0
Xaegr #
1. При даблклике на скрипте он открывается блокнотом.
2. Запуск скриптов по умолчанию запрещен. Чтобы включить надо выполнить особую команду под администратором. Чтобы узнать команду — надо взглянуть на хелпу — супер фильтр :)
0
uglock #
1. Есть удаленный помощник. Не URL правда нужно скидывать, а файлик. Но десктоп Юзера вы в итоге увидете.

2. То, что вы не пользуетесь RDP не значит что им не пользуются. RDP в разы быстрее и удобнее того же VNC. Особенно на медленных соединениях. Позволяет прицеплять локальные принтеры, диски и т.п. В новой версии обещали поддержку DirectX.
+1
VlK #
Ну, это не мой мир; у меня баш да питон из коробки :)
0
amirul #
Все еще не понимаю Вашего сарказма. В моем мире Jscript, VBscript, а теперь еще и PowerShell «из коробки». И если питон еще может потягаться с ними по удобству, то баш просто откровенно сливает.
+1
VlK #
А чего вы взяли, что я саркастичен? Нет, в вашем мире тоже появился шелл, и это очень мило, пятнадцать-то лет спустя…
+2
amirul #
В «нашем мире» уже 20 лет есть и шелл и скриптинг. Просто Вы о них, похоже, не знаете.
–3
VlK #
Ну про скриптинг на VB я много хорошего слышал и видел много потрясающих специалистов по сборке формочек. Про шелл… Это вы про batch-файлы и cmd.exe? И вы это серьезно?
0
rPman #
гуглите про Windows scripting Host, доступно аж для win98 и примерно с тех времен, позволяет писать скрипты из 'каропки' на JS и VB.В скриптах доступно использование объектов ActiveX (а это значит доступно все, от баз данных доинтернета, хотя графический интерфейс как то не замечал), при очень вдумчивом изучении документации MSDN можно очень неплохо автоматизировать работу администратора и не только.
0
Guderian #
хотя графический интерфейс как то не замечал

Обычно использовались HTA (HTML Applications).
0
amirul #
Потрясающе все таки, как некоторые линуксоиды умеют с надменным видом нести чушь. Начиная с того, что спутав VB и VBS Вы показали, что вообще не понимаете о чем речь и заканчивая тем, что bash таки есть в винде, просто он слишком убог для скриптинга, а в качестве шелла вполне сгодится и cmd (тем более, что все те утилиты, с которыми баш просто отвратно выглядит, но без которых он беспомощнее ребенка, тоже есть на винде — POSIX сертификация есть, как никак).
+3
amirul #
Вы попросту предвзяты. Сравниваете one-liner с 52-строчником (ну на питоне в общем то one-liner-ы и невозможны). Хотите читаемости — перепишите то же самое так:
Copy Source | Copy HTML
  1. [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
  2.  
  3. $source = "x:\source"
  4. $target = "x:\target"
  5.  
  6. foreach ($file in dir $source -r -inc *.jpg, *.gif, *.png) {
  7.     try {
  8.         $image = new-object System.Drawing.Bitmap $file.FullName
  9.         $targetdir = "{0}\{1}x{2}" -f $target, $image.Width, $image.Height
  10.         if (!(test-path $targetdir)) {
  11.             md $targetdir
  12.         }
  13.         copy $file $targetdir
  14.  
  15.         Write-Host $file -> $targetdir
  16.     } catch {
  17.         Write-Host $file " **IS NOT COPIED**"
  18.     }
  19. }
  20.  

Вы действительно считаете, что питоновский вариант читабельней? Но с другой стороны, лично я б вместо перекладывания файлов по каталогам, взял бы Wia.ImageFile и проставил нужные теги
0
susl #
добавьте это в статью, а то будет еще пару десятков коментариев о том какой же ужасный однострочник…
0
amirul #
Статья не моя — я просто вступился на правах мимопроходил-а
0
Guderian #
Побольше бы таких мимопроходцев :)
0
VlK #
Да я не сравниваю, просто говорю, что one-liner выглядел убого, ничем не лучше оригинальных perl`измов.

Все же, согласитесь, это просто еще один Шелл, только с очень большим опозданием.
0
Guderian #
> Да я не сравниваю, просто говорю, что one-liner выглядел убого, ничем не лучше оригинальных perl`измов.

А Вы попробуйте ради интереса написать тоже самое на баше в виде пайпа. Уверяю, хитросплетения всяких grep/awk оставит те же самые впечатления у людей непосвященных в таинство действия.

> Все же, согласитесь, это просто еще один Шелл, только с очень большим опозданием.

Тогда можно было бы сказать, что питон — просто еще один язык, только с очень большим опозданием. Но это не совсем так. И python и powershell несут нам немало новых возможностей.
0
amirul #
Объектные пайпы это очень круто, конечно (без всякой иронии), просто данный конкретный пример не слишком удачный. Вот помнится классический пример из «Unix Hater's Handbook» (вывести рекурсивно все файлы с расширением *.el, которые не имеют соответствующего им *.elc в том же каталоге) решается очень даже наглядно. Более того, формат вывода остается в точности таким же как и у простого dir.

И, да
sort(find((dir /b folder1\*.txt && dir /b folder2\*.txt), «text»))

Оно бы выглядело не так :-)
0
Guderian #
Пример однозначно в статью, спасибо)
0
susl #
> (dir /b folder1\*.txt && dir /b folder2\*.txt) | find «text» | sort
>…
> В функциональных языках программирования это могло бы выглядеть так:
> sort(find((dir /b folder1\*.txt && dir /b folder2\*.txt), «text»))

но скорее вот так:
(dir /b folder1\*.txt && dir /b folder2\*.txt) |> find «text» |> sort
ближе к оригиналу :)
0
Guderian #
> ближе к оригиналу

Это, если не секрет, к какому оригиналу ближе? :) Потому как у меня в уме была больше классика ФП в виде LISP и скобочки, скобочки, еще больше скобочек :)
0
susl #
имелось ввиду к пайповому оригиналу :)

вот этому: "(dir /b folder1\*.txt && dir /b folder2\*.txt) | find «text» | sort"
+1
taliban #
осталось написать сортовку на Jscript-е и bash-е и тогда все будут довольны и «неделя сортировки изображений» закончится… у вас всего 3 дня, товарищи!
+5
amirul #
Jscript как то так:
Copy Source | Copy HTML
  1. var Source = "x:\\source";
  2. var Target = "x:\\target";
  3.  
  4. var Fso = new ActiveXObject("scripting.FileSystemObject");
  5. var Image = new ActiveXObject("Wia.ImageFile");
  6.  
  7. function foreach(Collection, Proc) {
  8.     for (var enumerator = new Enumerator(Collection); !enumerator.atEnd(); enumerator.moveNext())
  9.         Proc(enumerator.item());
  10. }
  11.  
  12. function SortImages(Folder) {
  13.     foreach(Folder.Files, function (file) {
  14.         try {
  15.             Image.LoadFile(file);
  16.             var targetPath = Target + "\\" + Image.Width + "x" + Image.Height + "\\";
  17.             if (!Fso.FolderExists(targetPath)) {
  18.                 Fso.CreateFolder(targetPath);
  19.             }
  20.             Fso.CopyFile(file, targetPath);
  21.             WSH.Echo(file + " -> " + targetPath);
  22.         } catch (e) {
  23.             WSH.Echo(file + " **WAS NOT COPIED**");
  24.         }
  25.     });
  26.     foreach(Folder.SubFolders, SortImages);
  27. }
  28.  
  29. SortImages(Fso.GetFolder(Source));
  30.  
–5
xonix #
PowerShell не нравится, какая-то ацкая смесь перла, тикля и бейсика. Нафига? Уж лучше б MS JSсript command-line сделали, всяко удобнее и проще. Ну да ладно, я все равно python-way предпочитаю =)
За статью однако-же спасибо.
–1
uglock #
Вы не поверите, но Jscript скриптинг был в Windows начиная эдак винды с 98-й.
+1
xonix #
Перечитайте внимательнее мой комментарий. Там написано русским по белому «JSсript command-line».
Я имел в виду Jscript shell, как например в Firebug. Про cscript и wscript я, разумеется, в курсе, писал несколько утилит. Кстати, имею доложить, что он (jscript) тормоз тот еще, существенно уступает тому же python по скорости.
0
Guderian #
По поводу скорости целиком поддерживаю, хотя как язык он весьма интересен. Функциональный, динамический. От таких скорости обычно не ждешь, но можешь ожидать интересных решений :) Что до Jscript, то в основе PS лежат достаточно строгие CLS-языки, которые становясь частью интерактивного действа были вынуждены обрасти всякой синтаксической мишурой. Я боюсь, что будь на его Jscript, он выглядел бы не лучше :)
+1
xonix #
>Я боюсь, что будь на его Jscript, он выглядел бы не лучше :)

Придерживайся MS стандартов всё было бы ок. ECMAscript еще никто не отменял. А так, Вы, возможно, правы.
0
Guderian #
А в чем Jscript не соответствует ECMA-262? Мне, право, интересно. Да и не имеет ECMAscript никакого представления, что такое stdin/stdout, так что для пайпов его не так просто использовать.
+1
xonix #
Да хотя бы вот
javascript.ru/blog/Dmitry-A.-Soshnikov/Tonkosti-ECMA-262-3.-CHast-5.-Funkcii.
(Ctrl-F -> Реализация ECMAscript от Microsoft – Jscript, встроенная в Internet Explorer, на текущий момент (вплоть до версии Jscript 5.8 – IE8) имеет ряд багов)

Ну и пресловутое

Wscript.echo([1,].length)

>cscript /nologo 1.js
2

Вообще нюансов много. Но я скорее не о том, а о хорошо известном подходе MS, который в вольном пересказе звучит так: «Если мы видим как улучшить стандарт, мы не колеблясь делаем это». Но ведь стандарты для того и есть чтоб им следовать, хороши они или нет. Ярким проявлением этой философии был механизм рендеринга блочной модели страницы браузера, который в IE кардинально отличался от стандарта, ну и так далее. Оттуда и пляски с режимами совместимости и т.д.
0
Guderian #
Большое спасибо за информацию, буду вникать. Что до нюансов, то они мне напоминают тезисы из справочника юного холиварщика)) Честно скажу, мне совершенно не интересен подход MS, их моральный облик и данные финансовой отчетности) Для меня это данность, а вот бороться с ней как раз и поможет та информация, которой Вы меня вооружили :)
0
xonix #
а так уж важны пайпы эти самые? в питон-шеле их как-то нет, и в firebug шеле — нет, и ничего вроде. А так получается что и пайпы есть, т.е. удобно писать one-liners, и ООП с классами и прочими вкусностями есть — соизволь использовать, а в сумме что? Правильно — те самые монструозные one-liner-коды которые мы имели честь созерцать в сообщении уважаемого автора.

Предвосхищая реплики «Больше фич хороших и разных», «Не хочешь — не используй» и т.д. вежливо не соглашусь и сошлюсь на сверх-гибкий и приспособленный по сути для тех же целей Perl, о поддержке и возможности прочтения кода через месяц после написания которого уже ходят легенды.
–1
AndreyTS #
да это обычный .NET язык с пайпами, оформленный в традициях шеллов. никакого перла и Basic-а, см выше про сравнение однострочника с многострочниками.
0
xonix #
>никакого перла

$_ и вообще префиксование переменных $, всякие @ и прочий синтаксический шум…
0
Guderian #
В шеллах этого «префиксирования» навалом, включая cmd.exe. Даже в SQL его можно найти. Тогда можно сказать, что это смесь шелла, SQL, C# :)
+1
xonix #
я не возражаю )

p.s. в python и ruby шелле, как не сложно было бы догадаться, префиксования нет )
–1
Guderian #
Согласитесь, что ieval('_.basename()… — это не верх синтаксического совершенства )
0
xonix #
ну вот опять, люди судят о языке/подходе основываясь на 1, далеко не лучшем примере применения.
–1
Guderian #
Ценность данного сообщения была бы стократ выше, будь в нем второй, гораздо более лучший «пример применения»)) Я, увы, так устроен, что оперирую фактической информацией и руководствуюсь презумпцией качественности приводимых примеров, пока не доказано обратное. Поэтому у меня пока нет оснований считать, что пример pawnhearts в чем-то хуже другого, которого я пока не видел)
0
xonix #
Ёлка-палка, а Вы видели вообще самое первое сообщение с которого пошел весь сыр-бор с сортировкой картинок по размерам? там как-раз python-код.
0
Guderian #
Мало того, что видел, а еще и подробно описал в посте отличие подходов, поэтому сравнение некорректно. Если бы стояла задача сравнить эти два кода, то я бы просто написал простой императивный скрипт на c# и вопрос «префиксования» не стоял бы в принципе, не находите?
–1
xonix #
ух блин… сорри, проглядел ники…
Короче я лишь хочу сказать, что проведенное Вами сравнение оказалось (на мой глубоко субъективный взгляд) не в пользу PowerShell. И вообще я не рекомендовал бы его использовать только потому что он стоит в системе по умолчанию, примерно так же как и с IE. Да, IE открывает сайты, то FF (Chrome/Opera) то лучше… так и здесь. Ну хоть 1 аргумент использовать PS кроме того, что ставится по дефолту?
+4
pawnhearts #
вариант на bash
for f in $(find ./ -type f |xargs -n1 file |grep image |awk -F':' '{print $1}' |xargs -n1 gm identify); do path=$(echo $f|awk '{print $3}'); mkdir $path >/dev/null; cp $f $path; done
0
pawnhearts #
вместо awk '{print $3}' можно использовать cut -d' ' -f3
0
pawnhearts #
поясление на всякий случай find ./ -type f рекурсивно находит все файлы в текущем каталоге и подкаталогах, xargs -n1 file — применить к каждому команду file которая выводит имя файла: тип файла, grep image отфильтровать только те строки, в которых тип содержит слово image, далее избавляется от типа файла, оставляем только снова имена, применяем к каждому gm identify которая выводит информацию об изображении, с помощью конструкции for f in $(command); do ...;done для каждой строки из результата делаем последовательность команд. сначала вырезаем колонку с информацией о размере изображения и записываем в переменную path; создаем каталог path(если он уже создан — оно ругнется, поэтому мы перенаправляем ругань в /dev/null, там опечатка, должно быть mkdir $path 2>/dev/null (чтобы не выводить сообщения об ошибках), дальше тоже опечатка, должно быть cp $(echo $f |awk '{print $1}') $path; — копируем первую колонку(пуль к файлу) в $path
–1
pawnhearts #
что хорошо в bash так это короткие имена команд, наследие телепайтов, плохо в powershell — очень длинные имена классов из .net(в visualstudio от этого должно как-то спасать автодополнение, а в powershell?).
по поводу объектной ориентированности, к примеру gm identify -verbose filename
выводит:
Image: cole-gerst-blog.jpg
Format: JPEG (Joint Photographic Experts Group JFIF format)
Geometry: 576x360
Class: DirectClass
Type: true color
Depth: 8 bits-per-pixel component
и т.д.
чем не объект? далее мы просто вырезаем интересующую строчку gm identify -verbose cole-gerst-blog.jpg |grep Geometry|cut -d: -f2
0
Guderian #
Большое спасибо за хороший пример с пояснением.

плохо в powershell — очень длинные имена классов из .net(в visualstudio от этого должно как-то спасать автодополнение, а в powershell?)

Для автодополнения можно использовать PowerTab. Ну а длинные имена классов и прочий функционал надо постепенно заворачивать в скриптлеты или конвертеры. Как, например, в PowerShell Community Extensions есть Import-Bitmap вместо моих сложных конструкций с инстанцированием System.Drawing.Bitmap. Или как по аналогии с выражением [xml]"?<root><item>item 1</item></root>" можно сделать конвертер [image]. Имхо, проблема PS в том, что он пока не накопил достаточного количества «синтаксического сахара», чтобы обернуть все те возможности, что есть в .NET FCL.

чем не объект? далее мы просто вырезаем интересующую строчку gm identify -verbose cole-gerst-blog.jpg |grep Geometry|cut -d: -f2

Может тем, что я не могу делать так: $file.Directory.GetAccessControl().SetAuditRule(...) или так:

Copy Source | Copy HTML
  1. ls *.jpg | foreach {
  2.     $image = new-object System.Drawing.Bitmap $_.FullName;
  3.     $graphic = [System.Drawing.Graphics]::FromImage($image);
  4.     $graphic.DrawLine(...);
  5.     $graphic.FillPolygon(...);
  6.     $graphic.DrawString(...);
  7.     $image.Save(...)
  8. }

Все-таки полноценные объекты и их текстовая сериализация (которая далеко не всегда и далеко не везде) — это не совсем одно и то же.
–1
Xpeh #
>чем не объект?

Прежде всего тем, что допускает ошибки. Что, если в EXIF встречается слово Geometry? Что, если используемая версия GraphicsMagick выводит geometry (с маленькой буквы) вместо Geometry?
+1
pawnhearts #
grep -i geometry\:
но особенность GraphicsMagick в том что они не меняют параметры и их вывод от версии к версии, отличие от imagemagick основное — в стабильности api, только-добавляют новые
0
amirul #
Я даже не знаю за что я больше люблю башевые однострочники (да и скрипты надо сказать ненамного лучше) — то ли за то, что их без поллитры не расшифруешь, то ли за то что они никогда не работают.
Вы там ниже задавали вопрос: а чем текст — не объект. Вот пара тест кейсов для Вашего решения:


Пара очевиднейших примеров и я вижу уже как минимум три ошибки.
+2
pawnhearts #
в моем примере было gm identify -verbose, а не gm identify
gm identify test\ invalid.jpg |grep Geometry будет иметь errorlevel >0 и не выдаст ничего
т.е. в скрипте мы пишем
geo=$(gm identify test\ invalid.jpg |grep Geometry\:) && next_action
next_action выполниться только если errorlevel у grep == 0
0
amirul #
в моем примере было gm identify -verbose, а не gm identify

Нет, что ж я слепой что ли? Написано ж прямым текстом:
xargs -n1 gm identify


И этого
&& next_action
тоже нет

Что же до verbose — файлы таки могут содержать символ перевода строки в имени — все опять сломалось. В целом сериализация/десериализация в raw-text работает довольно плохо. Нужно хотя бы внимательно эскейпить спец символы, чем никто не занимается.
+2
pawnhearts #
а вот ещё пример на питоне:
from ipipe import *
import Image
for name,img in iwalk(dirs=False) | ieval('_.basename(),Image.open(_)',errors='drop'):
  img.resize((img.size[0]/2,img.size[1]/2)).save('/tmp/'+name)
0
amirul #
Хороший пример (я не шучу — действительно клевый), вот только он делает совершенно не то, что пример из поста.
0
pawnhearts #
from ipipe import *
import Image, os, shutil
for img in iwalk(dirs=False) | ieval(Image.open, errors='drop'):
  path = '%dx%d' % img.size
  if not os.path.exists(path): os.makedirs(path)
  shutil.copyfile(img.filename, path)

как вариант:
from ipipe import *
import gtk, os, shutil
for f,img in filter(iwalk(dirs=False) | ieval('_,gtk.gdk.pixbuf_get_file_info(_)'):
  path = '%dx%d' % (img[1],img[2])
  if not os.path.exists(path): os.makedirs(path)
  shutil.copyfile(f, path)
0
pawnhearts #
второй вариант неправильный, правильно:
from ipipe import *
import gtk, os, shutil
for f,img in iwalk(dirs=False) | ieval('_,gtk.gdk.pixbuf_get_file_info(_)'):
  if img:
    path = '%dx%d' % (img[1],img[2])
    if not os.path.exists(path): os.makedirs(path)
    shutil.copyfile(f, path)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.