Интересные примеры на PowerShell

perl Недавно натолкнулся вот на этот пост. В нём приведено несколько интересных примеров того “как в Perl одна-две строчки кода могут сделать больше, чем десять строк в каком-нибудь другом языке программирования”😉 Мне показалось что будет интересно сравнить как эти примеры выглядели бы на PowerShell😉 И главное – это может быть полезно тем кто уже знает Perl но сейчас изучает PS.

Как известно PowerShell очень молодой язык, и разумеется он унаследовал множество элементов других языков, и следовательно местами схож с многими из них. Я часто слышу о коде PowerShell фразы типа “О, да они же украли PHP!”, “Это же C# с более простым синтаксисом”. Но по-моему больше всего PowerShell похож на Perl. Это и не удивительно – Perl был одним из любимых языков авторов PS, и это здорово — многим хорошим особенностям в PS мы обязанны именно Perl’у.

Hats off to superstar Larry Wall and Perl, very few people and technologies that have had the level of (positive🙂 ) impact these 2 have had on the industry.  The world is a better place because that guy was born!

Jeffrey Snover


Это была отмазка😉 Теперь перейдем к коду🙂

Сразу скажу – мне кажется это просто удачная подборка попалась, многие примеры удалось записать на PS сильно короче, и главное – понятнее (субъективно конечно🙂 ). В очень многих областях Perl легко даст фору PowerShell’у в плане компактности кода, может быть даже и некоторые из этих примеров на Perl можно записать гораздо более красиво, так что буду рад если знающие Perl люди оставят свои конструктивные комментарии🙂

Я цитирую только описания, код на Perl можно посмотреть в оригинальном посте.

1. Проверить, существует ли элемент (первый аргумент функции, передается по значению) в массиве (второй аргумент функции, передается по ссылке).

#Встроенный оператор -contains            
$array -contains $element            

2. Удалить из массива @arr элементы, которые есть в массиве @skip.

#Вопросительный знак - алиас для where            
$arr | ? {$skip -notcontains $_}

Вместо пункта 3 я написал красивый (субъективно разумеется😉 ) фильтр:

filter Replace-Words            
{            
    foreach ($arg in $args) {            
        $pair = $arg.split("=",2) #Разрезаем аргумент на 2 части по знаку             
        $_ = $_ -replace $pair[0],$pair[1] #Заменяем вхождения в строке            
    }            
    $_ #Выдаём результирующую строку            
}

Использовать например так:

$text = Get-Content .\test1.txt            
$text | Replace-Words плохое=хорошее яблоки=груши | Set-Content .\test1.txt

4. Вывести список имен файлов и каталогов в заданной директории, отсортированный по дате последнего доступа. Обычно глобы сортируют список по имени файлов и каталогов. Для сортировки по дате последнего изменения, заменить цифру 8 на 9.

В PS для сортировки по дате изменения надо заменить не 8 на 9, а LastAccessTime на LastWriteTime😉

ls | sort lastAccessTime

5. Удалить повторяющиеся элементы в массиве.

$arr | select -Unique

6. Перемешать элементы массива

$arr | sort {Get-Random}

Командлет Get-Random появился только в PS 2.0, в 1.0 можно сделать так:

$r = New-Object random            
$arr | sort {$r.next()}

7. Выбрать случайный элемент в массиве можно как минимум двумя способами. Можно перемешать элементы, как в предыдущем примере, и выбрать нулевой, а можно в одну строчку:

$arr | Get-Random

8. Аналог PHP функции urlencode.

[System.Uri]::EscapeUriString(http://проверка)
Опубликовано в PowerShell, Tips. Метки: . 26 комментариев »

комментариев 26 to “Интересные примеры на PowerShell”

  1. bass Says:

    Спасибо за Вашу работу! Только начал изучать Power — статьи очень помогают.
    Недавно написал скрипт для синхронизации двух каталогов, для сравнения массивов использовал циклы. А тут у Вас сего одна строчка:
    2. Удалить из массива @arr элементы, которые есть в массиве @skip.

    $arr | ? {$skip -notcontains $_}

    Попытался применить у себя:
    $e1=dir $destination | sort -Property name | select name
    $e2=dir $sorce | sort -Property name | select name
    $e2 | ? {$e1 -notcontains $_}
    Работает некорректно. Может подскажете в чём корень проблеммы? Спасибо ещё раз.

    • Xaegr Says:

      bass, дело в том что разные объекты даже с одним одинаковым свойством не равны друг другу. В отдельных случаях, программисты реализуют «интерфейс» (надеюсь не путаю терминологию) CompareTo, благодаря этому можно например сравнивать объекты строк и т.п. Для файлов же этот интерфейс не был реализован. Это вобщем логично — так как не очевидно по каким свойствам сравнивать.
      Впрочем в данном случае это несложно обойти, вытащив из объектов файлов лишь их имя:
      $e1 = dir .\test1 | select -ExpandProperty name
      $e2 = dir .\test2 | select -ExpandProperty name
      $e1 | ? {$e2 -notcontains $_}

      • Xaegr Says:

        Кстати, еще один метод для сравнения объектов, особенно полезен если надо сравнить более чем по одному свойству:
        Compare-Object (ls test1) (ls test2) -property name, length

      • bass Says:

        Спасибо. Естественно заработало. Осталось осмыслить набранное.

  2. Mark Says:

    Полезный пост, спасибо автору! До недавнего времени считал Perl одним из самых простых, удобных и функциональных ЯП, пока не начал изучать PowerShell. Действительно многие вещи на нём можно сделать проще. Переписал все свои любимые Perl-скрипты на PS, но как дело дошло до применения столкнулся с тривиальной задачей, которую не смог достойно реализовать на PS. Суть такова:
    Как из cmd запустить такой скрипт:
    powershell c:\s vc.ps1
    Пробовал как в обычной консоли «c:\s vc.ps1», ‘c:\s vc.ps1’
    не помогло. Если поставить обратный апостроф перед пробелом, то всё хорошо, но это для меня не вариант.
    Та же ситуация и с передачей параметров, правда там одинарный кавычки спасают, параметр ‘s vc.ps1’ передаётся как один, но я передаю в скрипты от 1 до 1000 параметров, через Total Commander, который автоматом загоняет параметры в двойные кавычки.
    Буду очень благодарен за помощь!
    Изучаю PowerShell всего 3 день.

    • Xaegr Says:

      Для вызова скрипта путь к которому заключен в кавычки, следует использовать такую конструкцию:
      & «c:\my script.ps1» «Первый параметр с пробелом» «Второй параметр с пробелом»
      иначе он интерпритируется как строка, и просто выводится на экран.
      Двойные и одинарные кавычки легко заменяют друг друга. Единственное — в двойных расширяются переменные, а в одинарных нет. Сравните например
      «Текущая папка $pwd»
      ‘Текущая папка $pwd’

  3. Mark Says:

    Спасибо за ответ! Конструкция: & «c:\my script.ps1″ работает только в оболочке PowerShell. При передаче в cmd powershell &«c:\my script.ps1″, просто запускается оболочка Power. Ну да ладно, я всё равно никогда не допускаю провокационных символов в полных именах скриптов.
    Что касается передачи параметров самому скрипту, действительно, одинарные и двойные играют одну туже роль в плане передачи параметров. Интерполяция переменных происходит в двойных кавычках, точно также как и в Perl, что меня сразу и обрадовало.
    Но моя проблема всё же не решена.
    Следующая строка в оболочке PowerShell выполняется отлично: c:\script.ps1 «file 1.txt» file2.txt, но не выполняется из командной строки windows в случае:
    powershell c:\script.ps1 «file 1.txt» file2.txt, передаётся три параметра, а не два. А вот через такую командную строку: perl c:\script.pl «file 1.txt» file2.txt передаётся всё как надо. В этом то и проблема.
    И вопрос можно ли запускать PoSh скрипты по двойному щелчку мыши, если да то как, тогда скорее всего это будет выходом из ситуации.

    • Xaegr Says:

      А заключить всю команду в кавычки не поможет?
      powershell ‘c:\script.ps1 «file 1.txt» file2.txt’
      Или так: powershell -command c:\script.ps1 «file 1.txt» file2.txt
      По двойному клику — это мера безопасности. При желании можно проассоциировать скрипты с powershell вручную.

  4. Mark Says:

    В продолжении темы Perl vs PowerShell, хочу узнать как обстоят дела у PowerShell с портативностью. Для запуска Perl скрипта на сторонней машине нужен perl интерпретатор 44кб и библиотека perl58.dll 796кб и Perl всегда с тобой. Если нужны все дополнительные модули копируем всю папку и Perl+модули всегда с тобой.
    PowerShell зависим от NET Framework, ну если учитывать, что DotNet стоит практически везде, можно ли иметь PowerShell с собой, например на Flash и пользоваться этим замечательным инструментом на других машинах?

    • zorion Says:

      Если мы учитываем что DotNet установлен везде, то можем уже учитывать что и обновления ставятся везде. Так что PoSh тупо везде уже установлен🙂

  5. Mark Says:

    Заключение всей команды в кавычки передаёт строку в оболочку PS. Использование параметра -command даёт аналогичный результат. Путём перебора всех возможных способов расстановки кавычек проблему решила следующая строка:
    powershell c:\test.ps1 ‘file 1.txt’ file2.txt
    или
    powershell c:\test.ps1 \»file 1.txt\» file2.txt
    В общем, по непонятным для меня причинам cmd не передаёт двойные кавычки powershell, но передаёт любым другим программам. Звучит парадоксально.
    Буду разбираться дальше т.к. это ненормально.
    Ещё раз спасибо за помощь!
    А это некоторые примеры из поста в моём исполнении:
    Выбрать случайный элемент в массиве
    $rand=$arr[rand($#arr)];
    Удалить из массива @arr элементы, которые есть в массиве @skip
    %seen=(); #Хэш для проверки совпадений
    foreach $item(@skip){
    push(@arr, $item) unless $seen{$item}++;
    }
    Замена строк в тексте делается
    $text=~tr/$math/$replace/;
    или подобие filter Replace-Words
    foreach $arg ($ARGS){
    $arg=~s/(.*)=(.*)/$2,$1/g; #использую
    } #регулярное выражение
    Или так на PowerShell
    $_ = $_ -replace ‘(.*)=(.*)’, ‘$2,$1’
    Перемешать элементы массива
    use List::Util qw/shuffle/;
    @arr = shuffle @arr;
    Как видно PowerShell всё же удачней.

  6. Xaegr Says:

    >В общем, по непонятным для меня причинам cmd не передаёт двойные кавычки powershell, но передаёт любым другим программам.
    Он не передает их никому. Если cmd видит блог в двойных кавычках, то он передает его программе как один аргумент, с пробелами, без кавычек.
    c:\Root>Bin\testarg.exe «123 456»
    arg[0] is [Bin\testarg.exe]
    arg[1] is [123 456]

    Чтобы обойти эту проблему, можно использовать одинарные кавычки, их понимает только PowerShell🙂
    Если дела совсем плохи, и надо передать какую то мега конструкцию которую никак не передать через парсер Windows, можно использовать ключ -encodedCommand у PowerShell.exe

  7. Mark Says:

    >Он не передает их никому. Если cmd видит блог в двойных кавычках, то он передает его программе как один аргумент, с пробелами, без кавычек.
    Согласен, неправильно выразился, имел в виду то, что при передаче двух параметров, где один из них имеет пробел PS принимает три, а другие программы два.
    Короче говоря, в win cmd всегда для передачи параметров с пробелами используются дв. кавычки, в случае с PS спасают одинарные, потому что они то как раз передаются и он их понимает, а двойные убираются и их можно передать только через escape-пос-ть.
    Параметр -encodedCommand действительно работает, в оболочке PS пишем:
    $cl = ‘c:\script.ps1 «file 1.txt» file2.txt’
    $b = [System.Text.Encoding]::Unicode.GetBytes($cl)
    $ec = [Convert]::ToBase64String($b)
    powershell.exe -encodedCommand $ec
    Такая конструкция передаёт два параметра. Но мне то надо из cmd, применил параметр -command «», работает, но передаётся опять три параметра, потому что в $cl дв. кавычки у file 1.txt куда-то пропадают.
    В общем вынес себе весь мозг, но желаемого результата, передать в PoSh скрипт параметр с пробелом в дв. кавычках через cmd, не получил, хотя был близок к цели.

    • Xaegr Says:

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

  8. Mark Says:

    Ну у меня всё же ещё остаётся надежда на осуществимость этой цели.
    Вообще, я ту поразмышлял и пришёл к выводу, что разработчики PS по всей видимости не рассчитывали, что кто-то будет передавать параметры через cmd в скрипт. Объясняется это тем, что имена файлов Windows могут содержать одинарные кавычки, но не могут двойные. Таким образом ввожу такую ком. строку:
    powershell c:\script.ps1 ‘file’.txt в итоге в скрипт не передаётся вообще ничего, хотя имя такое может существовать в ФС. Не знаю можно ли назвать это багом, но думаю в блог разработчиков можно сообщить, интересно что они ответят по этому поводу.

    • Xaegr Says:

      >чики PS по всей видимости не рассчитывали, что кто-то будет передавать параметры через cmd в скрипт
      Расчитывали. Более того, они знали что это будет далеко не просто, и именно поэтому сделали -EncodedCommand🙂
      >Не знаю можно ли назвать это багом, но думаю в блог разработчиков можно сообщить, интересно что они ответят по этому поводу.
      Лучше сообщите на connect.microsoft.com/powershell

  9. Mark Says:

    Предлагаю ещё несколько примеров на Perl (на мой взгляд, которые действительно демонстрируют его мощь), хотелось бы узнать можно ли реализовать это на PS:
    1) Создаём массив из 100 нулей:
    @arr=(0) x100;
    2) Удаляем элемент из массива:
    shift(@arr); #первый
    pop(@arr); #последний
    или вот очень удобно
    $#arr=2; #останутся первые три остальные del
    3) Добавляем элемент в массив:
    push(@arr, $var); #в конец
    unshift(@arr, $var); #в начало
    4) Ну и напоследок коронная фун-ция Perl — splice (ей можно делать с массивом или хэшом всё что угодно, имеет воз-ти, которые невозможно реализовать на таких мощных языках как C++ и C# и других).
    а) Удаляем $n элементов @arr
    splice(@arr,0,$n); #с начала
    splice(@arr,-$n); #с конца
    splice(@arr,$i,$n); #с i-той позиции
    б) Вставляем в массив эл-ты
    splice(@arr, $i, $j, @arr2); #вставка массива @arr2 после элемента $i, если $j=0, иначе если $j=1 вставка с замещением эл-та $i, иначе если $j=$n вставка с замещением от $i до $n.

    ЗЫ: Вот пожалуй за что нужно ценить и уважать Perl.

    • Xaegr Says:

      Отлично!🙂 Сейчас посмотрим что из этих вкусностей перешло в PowerShell и как🙂
      1)
      $arr=,0*100
      или чуть понятнее:
      $arr=@(0)*100
      2) Да, так ловко у PS не знаю как сделать🙂 С обычным массивом можно только так:
      $arr = $arr[1..($arr.length-1)]
      $arr = $arr[0..($arr.length-2)]
      Но учитывая что в perl это тоже функции, можно написать такие же в PS🙂
      3) Тут уже лучше:
      $arr+=$var
      $arr = ,$var+$arr
      4.а)
      $arr = $arr[$n..($arr.length-1)]
      $arr = $arr[0..($arr.length-1-$n)]
      $arr[0..($i)],$arr[($i+$n+1)..($arr.count-1)]
      4.б)… Тут запутался уже на стадии понимания🙂 Уж точно так коротко не выйдет🙂

      Итог по-моему следующий — в плане удобства базовой работы с массивами PS ничуть не хуже Perl, но вот набора таких функций для маленьких задачек конечно пока нет. Но радует что их несложно написать🙂

      Марк, спасибо!

      ЗЫ: IMHO в Perl наиболее выдающаяся область всё же не работа с массивами, а регулярные выражения.

  10. Mark Says:

    Ну вообще Perl создавался с целью обработки различных текстовых файлов, поэтому ему в этом равных наверное трудно найти и регулярные выражения в этом играют большую роль.
    Уже успел поработать с реджэксами в PS, в целом остался доволен, хорошая реализация. https://s-ssl.wordpress.com/wp-includes/images/smilies/icon_smile.gif А то бывает попадаются такие диалекты в некоторых языках или программах, с поддержкой РВ, что приходится разбираться в различиях и тратить на это лишнее время.

    А вот задачка по серьёзней, да и по практичнее.
    Условия таковы:
    Имеются файлы в одной директории (*.*), нужно создать для каждого файла одноимённую директорию (без расширения файла) и переместить туда этот файл.
    Файлы для которых нужно выполнить эту операцию предаются в командной строке без пути, только имя и расш-ние. И еще один нюанс, скрипт который будет всё это делать находится в любой другой диретории.
    Видел решение на VBS 30 строк кода, батник тоже жестоким получился.
    Вот мой вариант (до сих пор поражаюсь как он работает ведь файлы передаются без путей, а скрипт в другом каталоге):

    foreach $file (@ARGV){#@ARGV массив передан. файлов
    $path=$file; #запоминаем имя файла
    $file =~ s|\.[a-z0-9]*$||gi; #убираем расширение
    mkdir $file; #создаём папку
    move($path, «$file\\$path»); #ну и перемещаем
    }
    Как PS справится с такой задачкой?

  11. Xaegr Says:

    >Уже успел поработать с реджэксами в PS, в целом остался доволен, хорошая реализация.
    Это реализация .NET, она безусловно одна из лучших, как и Perl’овская🙂
    >А вот задачка по серьёзней, да и по практичнее
    Да ну, посерьезнее…🙂 Благодаря тому что PS работает с файлами как с объектами, это для него плевое дело🙂 Текст скрипта выполняющего такую же задачу:
    $args | gi | %{md $_.basename; mv $_ $_.basename}
    Помещаем например в sortfiles.ps1 и вызываем:
    PS D:\Temp> c:\root\sortfiles file1.txt file2.txt file3.txt
    Или даже так:
    PS D:\Temp> c:\root\sortfiles file?.txt

    >Видел решение на VBS 30 строк кода, батник тоже жестоким получился.
    Ну у этих только одно преимущество перед PS — пока бОльшая распостранённость🙂

  12. Mark Says:

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

    • Xaegr Says:

      Так, давайте определимся, файлы находятся в текущей директории, скрипт в какой то другой. Папки должны создаваться тоже в текущей директории. Или я что то неправильно понял?
      #Проверяем текущее состояние:
      PS D:\Temp\test> dir -Recurse | ft fullname
      FullName
      ———
      D:\Temp\test\file1.txt
      D:\Temp\test\file2.txt
      D:\Temp\test\file3.txt

      #Выполняем скрипт (находящийся в другой папке):
      PS D:\Temp\test> C:\Root\sortfiles file1.txt file2.txt file3.txt

      #Проверяем результат:
      PS D:\Temp\test> dir -Recurse | ft fullname
      FullName
      ———
      D:\Temp\test\file1
      D:\Temp\test\file2
      D:\Temp\test\file3
      D:\Temp\test\file1\file1.txt
      D:\Temp\test\file2\file2.txt
      D:\Temp\test\file3\file3.txt

  13. Mark Says:

    Xaegr, извиняюсь! То что описал не будет работать, ни через какую ком. строку. Я всё это осуществляю в Total-е.
    Создаю польз. команду со скриптом, вешаю на Hot-Key и передаю текущ. выделенные файлы. И в настройках этой команды есть параметр путь запуска, ничего не ставим, означает что подставиться путь тек. директории активной панели. Таким образом эффект будет таким, как если бы скрипт нах-ся в папке с файлами. Иначе как PS или Perl поняли бы, что это за файлы им передаются.
    Комментирую работу PS, если что не так поправите.
    Получает имена файлов, передаёт их get-item, эти имена извлекаются уже в виде объектов ФС с полными путями. Дальше опять отправляет по конвейру и по очереди создаёт папку с именем и перемещает туда текущий файл. Всё работает.
    Ну на Perl тоже при желании всё в одну строку можно запихать, basename тоже есть (не охота модуль было подключать), принципиальное отличие Perl от PS, Perl не работает с файлами как с объектами, а PS это сплошные объекты, вот есть свои плюсы. Ладно задачка действительно ни о чём. Есть её модификация, перем-ть file.wmv file.txt file.pptx и т.д. в одну директорию. Ну с этим PS думаю тоже справится без проблем.
    Вот задача действительно по серьёзней, нужно зарегистрировать службу Windows, используя WMI.
    Пробовал на PS, пока не получилось.
    Алгоритм:
    1) Создаём объект класса win32_service
    2) Описываем его свойства, name, type, path и т.д. (только основные).
    3) Ну и применяем метод Create
    После в списке служб должна появиться наша.
    Perl решение (ни в одной книге по Perl не найти да и в нете тоже не встречалось, сам до всего дошёл):
    $services =Win32::OLE->GetObject
    («winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2:Win32_Service»);
    $obj->inParameters->SpawnInstance_ if (defined $obj);
    $inParam->{Name} = «911»;
    $inParam->{PathName} = «c:\mysvc.exe»;
    $inParam->{DisplayName} = «mysvc»;
    $inParam->{ErrorControl} = «Normal»;
    $inParam->{ServiceType} = «Own Processs»;
    $inParam->{StartMode} = «Manual»;
    $inParam->{StartName} = «LocalSystem»;
    $services->ExecMethod_(«Create», $inParam);

  14. Xaegr Says:

    >Вот задача действительно по серьёзней, нужно зарегистрировать службу Windows, используя
    Я стараюсь использовать максимально простое и надежное решение задачи. Конечно без проблем можно сконвертировать этот код на PS, но я воспользовался sc.exe🙂

  15. Mark Says:

    Ну да через sc просто делается.
    Для себя открыл, что Perl код можно запускать из оболочки PS и даже интегрировать с PS кодом по аналогии с VBS. Раньше пользовался WSH скриптами, в которых можно было смешивать Perl и VBS, возможностей становилось больше.

  16. Mark Says:

    Здравствуйте Xaegr! На днях возникла задача, нужно запретить пользователю входить в систему несколько раз под одной и той же учётной записью одновременно на разных ПК. Может ли PS помочь каким-либо образом в данной ситуации? Заранее спасибо!


Обсуждение закрыто.

%d такие блоггеры, как: