Сравнение строк powershell: Работа со строками в PowerShell

PowerShell — сравнение двух текстовых файлов [скрипт]

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

Сразу покажу вам скрипт, чтобы не томить.

$f1=get-content -Path .\file1.txt
$f2=get-content -Path .\file2.txt
foreach ($objf1 in $f1)
{
                $objf1 = $objf1.tostring().Trim()
                $objf1 = $objf1.Replace(" ","")
                $objf1 = $objf1.ToUpper()
                $found = $false
                foreach ($objf2 in $f2)
                {
                               $objf2=$objf2.Replace(" ", "")
                               $objf2=$objf2.Trim()
                               $objf2=$objf2. ToUpper()        
                               if ($objf1 -eq $objf2)
                               {                                            
                                               $found = $true                               
                               }                             
                }
                if ((!$found) -and ($objf1 -ne ""))
                {
                               $result += $objf1.tostring().trim() + "`r`n"
                }
}
set-content -path resultFile.txt -value $result
#echo $result

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

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

Если кто не понял, каждая новая строчка в файлах – новый элемент для сравнения.

Разберем работу этого скрипта.

В первых двух строчках мы считываем наши файлы.

$f1=get-content -Path .\file1.txt
$f2=get-content -Path .\file2.txt

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

foreach ($objf1 in $f1)
{
$objf1 = $objf1.tostring().Trim()
$objf1 = $objf1.Replace(" ","")
$objf1 = $objf1.ToUpper()
$found = $false
...
}

Внутри первого цикла запускаем еще один, где перебираем элементы из второго файла, так же каждую строчку отчищаем и переводим символы в заглавные и сравниваем каждый элемент из второго файла с текущим элементом из первого. Если элемент найден – присваиваем переменной значение истина.

foreach ($objf2 in $f2)
{
   $objf2=$objf2.Replace(" ", "")
   $objf2=$objf2.Trim()
   $objf2=$objf2.ToUpper()        
   if ($objf1 -eq $objf2)
   {                                            
      $found = $true                               
   }                             
}

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

if ((!$found) -and ($objf1 -ne ""))
{
   $result += $objf1.tostring().trim() + "`r`n"
}

В самом конце скрипта записываем нашу переменную с результатами в файл.

set-content -path resultFile.txt -value $result

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

13 0

Поделитесь статьей с друзьями в соц. сетях, возможно, она будет им полезна.


Если вам помогла статья, вы можете >>отблагодарить автора<<


Ужасы PowerShell / Хабр

Мне часто приходится пользоваться PowerShell. Конечно, его создатели не имели никакого представления о прекрасном и эстетике. Уродливость PowerShell особенна видна при его сравнении, например, с Python. С другой стороны, как говорится, c лица не воду пить — работает и хорошо? Но нет, мне кажется в PowerShell есть по крайней мере пара моментов, которые фатально влияют на его практическое применение.

Расхлябанность

Это пока не фатальный пункт. Даже по сравнению с Python строгости меньше, мы можем забыть присвоить хоть какое-то значение переменной:

PS C:\> Write-host "Value is: $newvar"
Value is:
PS C:\>

Впрочем, даже ключевые слова иногда не проверяются:

Проблемы с наборами из одного элемента

Запишем файл и прочитаем его:

@"
One
Two
Three
"@ | Out-File x.tmp
$f = Get-Content "x.tmp"
Write-Host "Inside the file: $f"
Output:
Inside the file: One Two Three

Get-Content выдает массив строк. Расхлябанность языка приводит к тому, что при выводе они склеиваются — хорошо что хоть через пробел. Поэтому если мы хотим вывести первую строку, то нет проблем:

@"
One
Two
Three
"@ | Out-File x.tmp
$f = (Get-Content "x.tmp")[0]
Write-Host "Inside the file: $f"
Output:
Inside the file: One

Ну или если поменять файл:

@"
One
"@ | Out-File x2.tmp
$f = (Get-Content "x2.tmp")[0]
Write-Host "Inside the file: $f"
Output:
Inside the file: O

Что, простите? Куда делись ‘ne’? Ответ шокирует:

(Get-Content "x.tmp").GetType()
(Get-Content "x2.tmp").GetType()
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
True     True     String                                   System.Object

То есть раз в файле есть только одна строка, то зачем заморачиваться массивом, выбросим скобки и оставим только один элемент. Просто замечательно. Устанавливаем с помощью эксперимента в notepad, что string, string<br> это однострочный файл, а вот string<br><br> или string<br><sp> уже многострочные файлы — при этом в notepad все эти варианты визуально неотличимы друг от друга!

Теперь постараемся написать программу, которая выводит первую строку файла:

$f = Get-Content "x. tmp"
if ($f.GetType().Name -eq "Object[]") {
  $firstln = $f[0]
} else {
  $firstln = $f
}
Write-Host $firstln

Правильно? Нет! Если файл существует, но первая строка пуста (там нет даже пробела), то Get-Content возвращает $null! Кстати, найдите в официальной документации Microsoft описание этого поведения. Мне не удалось.

Это не единичный случай

Теперь вас не удивит поведение следующего кода:

$found = invoke-Sqlcmd -ServerInstance "Localhost" -Query "select * from sysdatabases"
$found.getType()
$found = invoke-Sqlcmd -ServerInstance "Localhost" -Query "select top 1 * from sysdatabases"
$found.getType()
$found = invoke-Sqlcmd -ServerInstance "Localhost" -Query "select top 0 * from sysdatabases"
$found.getType()

У меня в коде встречается, например:

$cnt = $found.Rows.Count
if ($found.getType().FullName -eq 'System.Management.Automation.PSCustomObject') 
  { $cnt = 1; }

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

Где-то плачут математики, для которых множество из двух элементов, и одного, и нуля элементов — это объекты одного «типа». Но создатели PowerShell похоже считают в парадигме: ноль, один, много.

Контрольный выстрел

Вам еще хочется программировать на PowerShell? Давайте поговорим об этом. Вот еще пример:

function something([string]$file, [string]$p2)  
{
  New-item $file
  return "always OK"
}
$res = something "xxx.tmp" "beta"
Write-host "Result: $res"
$res.GetType()

Что будет в $res? Вы уверены? Давайте проверим:

Какого черта? На самом деле в PowerShell функция возвращает не только то, что в return (это ключевое слово можно и не писать), а собирает все выводы по ходу выполнения. Вывод new-Item прилепился до того, что мы вывели в return.

Если в данном случае мы можем исправить это, дописав | Out-Null после New-Item, то при вызове произвольной сложной функции, написанной Васей Пупкиным, вы вообще не можете ничего гарантировать. Вообще ни-че-го.

По работе и в своем пет проекте мне пришлось написать много скриптов на PowerShell. В какой-то момент я решил замахнуться на Ubuntu и переписал огромное количество скриптов для MSSQL, Postgre и MySQL с PowerShell на Python. Когда это заработало под Ubuntu, я не мог уже остановиться, поставил pwsh для Ubuntu и убедился, что PowerShell хорошо под ней работает, и даже имитирует что все case-insensitive. Наконец я проверил Python-версию скриптов под Windows и только тогда успокоил ее величество ортогональность.

Кстати, Python скрипт везде стартует быстрее. PowerShell тупит доли секунды. Обычно это неважно, но когда на любое действие GUI вызывается скрипт то разница чувствуется.

непрерывная интеграция — сравнение строк не работает в powershell

спросил

Изменено
10 месяцев назад

Просмотрено
2к раз

Часть коллектива CI/CD

Я пытаюсь выполнить условие if else в powershell, используя сравнение строк. Я пробовал в соответствии с документацией, используя оператор -eq. Но ошибка ниже. Здесь «Build.Reason» — предопределенная переменная. Не уверен, почему он ищет имя командлета для переменной.

 Write-Host "$(Build.Reason)"
  если ($(Build.Reason) -eq "Вручную") {
      $temp = "https://url/api/qualitygates/project_status?&pullRequest=$(Build.Reason)"
      Write-Host "Вручную"
  } еще {
      Запись-хост "CI"
  }
 

Ошибка

 "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\d7af16d6-ce3e-4dec- a636-9447962fdac4.ps1 '"
Руководство
Вручную: термин «Вручную» не распознается как имя командлета, функции, файла сценария или исполняемой программы.
Проверьте правильность написания имени или, если был указан путь, убедитесь, что путь указан правильно, и повторите попытку.
В D:\a\_temp\d7af16d6-ce3e-4dec-a636-9447962fdac4.ps1:7 символ:5
+ если (Вручную -eq "Вручную") {
+ ~~~~~~
    + Информация о категории: ObjectNotFound: (Вручную:Строка) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : CommandNotFoundException
 
  • powershell
  • непрерывная интеграция

0

Похоже, $(Build. Reason) — это значение в стиле макроса , предоставляемое системой CI (это , а не конструкция PowerShell), которая расширяется и становится литералом частью кода до того, как PowerShell увидит ее.

Таким образом, если это значение должно рассматриваться как строка в результирующем коде PowerShell, вам необходимо заключить в кавычки; например:

 if ("$(Build.Reason)" -eq "Manual") { # ...
 

Обратите внимание, что если есть шанс, что $(Build.Reason) расширится до значения с встроенными " символами, они должны быть экранированы как `" . Аналогично, если значение содержит встроенные символы $ , следует использовать single -quoting, что может потребовать экранирования встроенных одинарных кавычек как '' .

Если это экранирование невозможно выполнить в источник , вы можете использовать дословную здесь-строку:

 если (@'
$(Сборка. Причина)
'@ -eq 'Вручную') { # ...
 

Важно : Закрытие '@ всегда должно быть в самом начале строки .

0

Зарегистрируйтесь или войдите в систему

Зарегистрируйтесь с помощью Google

Зарегистрироваться через Facebook

Зарегистрируйтесь, используя электронную почту и пароль

Опубликовать как гость

Электронная почта

Требуется, но никогда не отображается

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Нажимая «Опубликовать свой ответ», вы соглашаетесь с нашими условиями обслуживания и подтверждаете, что прочитали и поняли нашу политику конфиденциальности и кодекс поведения.

массивов — сравнение строк в PowerShell не работает

Задавать вопрос

спросил

Изменено
1 год, 2 месяца назад

Просмотрено
688 раз

Я пытаюсь сравнить строки из одного массива с каждой строкой из другого. Текущий метод работает, так как я тестировал его с более простыми переменными, и он правильно их извлекает. Однако строки, которые мне нужны для сравнения, похоже, не затронуты сравнением.

Метод следующий:

 $counter = 0
for ($num1 = 0; $num1 -le $serverid_oc_arr.Length; $num1++) {
     for ($num2 = 0; $num2 -le $moss_serverid_arr.Length; $num2++) {
          если ($serverid_oc_arr[$num1] -eq $moss_serverid_arr[$num2]) {
              перерыв
          }
          еще {
              $счетчик += 1
          }
          если ($ counter -eq $ moss_serverid_arr. Length) {
              $unmatching_serverids += $serverid_oc_arr[$num1]
              $счетчик = 0
              перерыв
          }
     }
}
 

Для каждой строки в первом массиве выполняется перебор всех строк во втором массиве и их сравнение. Если он находит равенство, он прерывается и переходит к следующей строке в первом массиве. Если это не так, для каждого неравенства он добавляется к счетчику, и всякий раз, когда счетчик достигает длины второго массива (что означает, что во втором массиве не было найдено равенства), он добавляет соответствующую строку в третий массив, который предполагается чтобы получить все строки, которые не соответствуют чему-либо во втором массиве в конце. Затем счетчик снова устанавливается в 0 и прерывается, чтобы перейти к следующей строке из первого массива.

Это все хорошо в теории и также работает на практике с более простыми строками, однако строки, с которыми мне нужно работать, являются идентификаторами серверов и выглядят так:

 289101b4-3e6c-4495-9c67-f317589ba92c
 

Таким образом, скрипт полностью игнорирует сравнение и просто помещает все строки из первого массива в третий и извлекает их в конце (иногда также некоторые случайные строки из первого и второго массива).

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

 $unmatching_serverids = $serverid_oc_arr | Где {$moss_serverid_arr -NotContains $_}
 

Кто-нибудь может заметить какую-нибудь ошибку, которую я могу где-то сделать?

  • массивы
  • строка
  • powershell
  • сравнение
  • сравнение

0

Проблема с вашим кодом в основном заключается в использовании -le вместо -lt , индекс коллекции начинается с 0 и свойство коллекции Length или Count начинается с 1 . Это приводило к добавлению значений $null в вашу коллекцию результатов.

В дополнение к вышесказанному, $counter никогда не сбрасывался, если не выполнялось следующее условие:

 if ($counter -eq $moss_serverid_arr.Length) { ... }
 

Вам нужно добавить $counter = 0 снаружи внутреннего цикла for, чтобы предотвратить это.

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

 $ ран = [случайный] :: новый ()
$ref = [System.Collections.Generic.HashSet[int]]::new()
# создать коллекцию из 100 GUID
$arr1 = 1..100 | ForEach-Object {[guid]::NewGuid()}
# выбрать 90 уникальных GUID из arr1
$обр2 = 1..90 | ForEach-Object {
    делать {
        $i = $ran.Next($arr1.Count)
    } до($ref.Добавить($i))
    $обр1[$i]
}
$result = foreach($i в $arr1) {
    $Найдено = foreach($z в $arr2) {
        если ($i -eq $z) {
            $правда; перерыв
        }
    }
    если (-не найдено) { $i }
}
$result.Count -eq 10 # => Должно быть `$true`
 

Кроме того, приведенное выше можно сократить до этого, используя метод .ExceptWith(..) из HashSet Class:

 $hash = [System.Collections.Generic.HashSet[guid]]: : новый ([руководство []] $ обр1)
$hash.ExceptWith([guid[]]$arr2)
$hash. Count -eq 10 # => `$true`
 

1

Рабочий ответ, который я нашел для этого, приведен ниже:

 $unmatching_serverids = @()
foreach ($элемент в $serverid_oc_arr)
{
    если ($moss_serverid_arr -NotContains $item)
    {
    $unmatching_serverids += $item
    }
}
 

Между ним и другими методами не видно явных отличий (особенно для цикла for, это просто упрощенный вариант), но почему-то это работает корректно.

Зарегистрируйтесь или войдите в систему

Зарегистрируйтесь с помощью Google

Зарегистрироваться через Facebook

Зарегистрируйтесь, используя электронную почту и пароль

Опубликовать как гость

Электронная почта

Требуется, но никогда не отображается

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Нажимая «Опубликовать свой ответ», вы соглашаетесь с нашими условиями обслуживания и подтверждаете, что прочитали и поняли нашу политику конфиденциальности и кодекс поведения.