Ответ 1
Чтобы ответить на ближайший вопрос с помощью решения PSv3 +:
(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName
-Directory
ограничивает совпадения с каталогами, -Recurse -Depth 2
рекурсирует до 3 уровней (дети, внуки, правнуки), Include
позволяет указать несколько фильтров (filename-component), а .Parent.FullName
возвращает полный путь родительских директорий. из сопоставленных dirs., используя перечисление членов (неявно доступ к свойствам элемента коллекции).
Что касается вопроса о бонусе: select-object {$_.Directory}
не работает,
потому что [System.IO.DirectoryInfo]
экземпляры, возвращенные Get-ChildItem
, не имеют свойства .Directory
, а только свойство .Parent
; Select-Object -ExpandProperty Parent
должен был использоваться.
В дополнение к возврату интересующей стоимости свойства -ExpandProperty
также обеспечивает существование свойства. Напротив, select-object {$_.Directory}
возвращает пользовательский объект с свойством, буквально называемым $_.Directory
, значение которого $null
, учитывая, что входные объекты не имеют свойства .Directory
; эти значения $null
печатаются как пустые строки в консоли.
Что касается более общего вопроса о PowerShell, эквивалентном LINQ .Any()
method, который указывает [с булевым результатом], имеет ли данный перечислимый (набор) какие-либо элементы/любые элементы, удовлетворяющие заданному условию:
Намеренно PowerShell не предлагает такого эквивалента, но поведение может быть эмулировано:
Использование оператора коллекции PSv4 + .Where()
:
Предостережение: для этого требуется сначала собрать всю коллекцию ввода в памяти, что может отрицательно сказаться на производительности и потреблении памяти.
(...).Where({ $_ ... }, 'First').Count -gt 0
...
представляет интересующую команду и $_ ...
условие интереса, применяемое к каждому входному объекту, где переменная PowerShell automatic $_
относится к входному объекту под рукой; Аргумент 'First'
гарантирует, что метод вернется после первого совпадения.
Например:
> @(1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0 # see if there at least 1 value > 1
True
Использование конвейера: проверка того, была ли команда произведена, по крайней мере, с одним объектом вывода [с условием]:
Преимущество решения на основе конвейера заключается в том, что он может действовать на выходе команды один за другим, так как он создается, без необходимости сначала собирать весь вывод в памяти.
-
Если вы не возражаете, что все объекты перечислены - хотя вам все равно, если есть хотя бы один - используйте Paolo Tedesco полезное расширение в полезный ответ JaredPar.
Нижняя сторона этого подхода заключается в том, что вам всегда нужно ждать (потенциально долговременной) команды для завершения создания всех выходных объектов, хотя логически - определение того, есть ли какие-либо выходные объекты, можно сделать, как только первый объект получен. -
Если вы хотите выйти из конвейера, как только один объект [соответствующий] был встречен, у вас есть две возможности:
-
[Ad-hoc: легко понять, но громоздко реализовать]
Включите конвейер в фиктивном контуре и используйтеbreak
для выхода из конвейера, и этот цикл (...
представляет команду, выход которой для тестирования, и$_ ...
соответствует условию):[bool] $haveAny = do { ... | % { $true; break } } while ($false) # unconditional
[bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)
-
[Использовать автономную функцию утилиты PSv3 +, которая нетривиальна для реализации]
См. Реализацию функцииTest-Any
ниже.
Он может быть добавлен в скрипты или ваш$PROFILE
.
-
PSv3 +: оптимизированная служебная функция Test-Any
Функция нетривиальна, поскольку , как и Windows PowerShell v5.1, PowerShell Core v6, нет прямого способа выйти из конвейера преждевременно, поэтому обходной путь, основанный на анализе .NET, и частный тип в настоящее время необходим.
Если вы согласны с тем, что должна быть такая функция, примите участие в беседе на GitHub.
#requires -version 3
Function Test-Any {
[CmdletBinding()]
param(
[ScriptBlock] $Filter,
[Parameter(ValueFromPipeline = $true)] $InputObject
)
process {
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
$true # Signal that at least 1 [matching] object was found
# Now that we have our result, stop the upstream commands in the
# pipeline so that they don't create more, no-longer-needed input.
(Add-Type -Passthru -TypeDefinition '
using System.Management.Automation;
namespace net.same2u.PowerShell {
public static class CustomPipelineStopper {
public static void Stop(Cmdlet cmdlet) {
throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
}
}
}')::Stop($PSCmdlet)
}
}
end { $false }
}
-
if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject))
по умолчанию используется значение true, если$Filter
не указано, и в противном случае оценивает фильтр (блок script) с объектом под рукой.- Использование
ForEach-Object
для оценки блока script гарантирует, что$_
связывается с текущим объектом конвейера во всех сценариях, как показано в PetSerAl " > полезный ответ здесь.
- Использование
-
Оператор
(Add-Type ...
использует тип ad-hoc, созданный с кодом С#, который использует отражение, чтобы генерировать одно и то же исключение, котороеSelect-Object -First
(PS v3 +) использует внутри, чтобы остановить конвейер, а именно[System.Management.Automation.StopUpstreamCommandsException]
, который с PS v5 по-прежнему является частным типом. Справочная информация здесь: http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
Большое спасибо PetSerAl за внесение этого кода в комментарии.
<сильные > Примеры:
-
> @() | Test-Any false
-
> Get-EventLog Application | Test-Any # should return *right away* true
-
> 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true
Фоновая информация
полезный ответ JaredPar и полезное расширение Paolo Tedesco не соответствуют одному: они не выходят конвейер, как только совпадение найдено, что может быть важной оптимизацией.
ОБНОВЛЕНИЕ: К сожалению, даже с PowerShell v5 нет прямого способа выйти из конвейера преждевременно. Если вы согласны с тем, что должна быть такая функция, примите участие в беседе на GitHub.
A наивная оптимизация ответ JaredPar фактически сокращает код:
# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
- Блок
process
вводится только в том случае, если в конвейере есть не менее 1 элемента.- Небольшое предостережение: по дизайну, если нет конвейера вообще, блок
process
все еще введен, а$_
установлен на$null
, поэтому вызовTest-Any
вне конвейера бесполезно возвращает$true
. Чтобы различать между$null | Test-Any
иTest-Any
, проверьте$MyInvocation.ExpectingInput
, который$true
только в конвейере: Спасибо, PetSerAlfunction Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
- Небольшое предостережение: по дизайну, если нет конвейера вообще, блок
-
$true
, записанный в выходной поток, сигнализирует о том, что найдено не менее 1 объекта. -
break
затем завершает работу конвейера и, таким образом, предотвращает излишнюю обработку дополнительных объектов. ОДНАКО, И ТАКЖЕ ВЫХОДИТ ЛЮБОЙ ЗАКРЫВАЮЩИЙ КОНТУР -break
НЕ предназначен для выхода из PIPELINE Спасибо, PetSerAl SUP > .- Если бы была команда выхода из конвейера, это куда-то.
- Обратите внимание, что
return
просто перейдет к следующему объекту ввода.
-
Поскольку блок
process
безоговорочно выполняетbreak
, блокend
достигается, только если блокprocess
никогда не был введен, что подразумевает пустой конвейер, поэтому$false
записывается в выходной поток, чтобы сигнализировать об этом.
В отсутствие надлежащей поддержки для выхода из конвейера, за исключением реализации нетривиальной функции Test-Any
выше, существует обходной путь:
-
Заключить любой конвейер, в котором вы используете указанную выше функцию
Test-Any
в манекене только один раз - очевидно, это неудобно и легко забыть:# The dummy loop ensures that the `break` inside Test-Any() # only breaks out of *it*. do { ... | Test-Any } while ($false)