#powershell #windows-10
#powershell #windows-10
Вопрос:
Я пытаюсь реализовать старую функцию Bash в Powershell (7 ) для отображения стека некоторого количества последних посещенных каталогов. Я нашел этот код из репозитория проекта Pscx. В конце концов, я хотел бы видеть это как автономную функцию.
Однако я сталкиваюсь с несколькими проблемами:
- Требуемый код находится в 2 файлах:
Pscx.CD.psm1
иMessages.psd1
. Их необходимо каким-то образом объединить в одну функцию, если это возможно. - Появляются несколько разных сообщений об ошибках, хотя команды все еще работают. Я не знаю, что с этим делать.
Cannot index into a null array.
At D:blahblahPscx.CD.psm1:137 char:21
if ($Pscx:Preferences['CD_GetChildItem'])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : NullArray
- Если вы в настоящее время находитесь где-то в середине стека и для выполнения a
cd
в новое местоположение, все последующие элементы стека перезаписываются, а не добавляются в конец списка (последний элемент). Как добавить acd <full path location>
в конец стека и не перезаписывать предыдущие элементы?
Хорошо, хорошо, вот объединенный код:
# From messages.psd1
ConvertFrom-StringData @'
SettingLocationF1=Setting location to: '{0}'
BackStackEmpty=The backward stack is empty.
ForeStackEmpty=The foreward stack is empty.
GoingToTheSameDir=Wherever you go, there you are!
NumOutOfRangeF1={0} is out of range.
'@
# From Messages.psd1
$backwardStack = new-object System.Collections.ArrayList
$forewardStack = new-object System.Collections.ArrayList
$ExecutionContext.SessionState.Module.OnRemove = {
Set-Alias cd Set-Location -Scope Global -Option AllScope -Force
}.GetNewClosure()
# We are going to replace the PowerShell default "cd" alias with the CD function defined below.
Set-Alias cd Set-LocationEx -Force -Scope Global -Option AllScope -Description "PSCX alias"
function Set-LocationEx
{
[CmdletBinding(DefaultParameterSetName='Path')]
param(
[Parameter(Position=0, ParameterSetName='Path', ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string]
$Path,
[Parameter(Position=0, ParameterSetName='LiteralPath', ValueFromPipelineByPropertyName=$true)]
[Alias("PSPath")]
[string]
$LiteralPath,
[Parameter(ValueFromRemainingArguments=$true)]
[string[]]
$UnboundArguments,
[Parameter()]
[switch]
$PassThru,
[Parameter()]
[switch]
$UseTransaction
)
Begin
{
Set-StrictMode -Version Latest
# String resources
Import-LocalizedData -BindingVariable msgTbl -FileName Messages
$ExtraArgs = @{}
if (($PSVersionTable.PSVersion.Major -lt 6) -or ($PSVersionTable.PSEdition -eq 'Desktop'))
{
$ExtraArgs['UseTransaction'] = $UseTransaction
}
function SetLocationImpl($path, [switch]$IsLiteralPath)
{
if ($pscmdlet.ParameterSetName -eq 'LiteralPath' -or $IsLiteralPath)
{
Write-Debug "Setting location to literal path: '$path'"
Set-Location -LiteralPath $path @ExtraArgs
}
else
{
Write-Debug "Setting location to path: '$path'"
Set-Location $path @ExtraArgs
}
if ($PassThru)
{
Write-Output $ExecutionContext.SessionState.Path.CurrentLocation
}
else
{
# If not passing thru, then check for user options of other info to display.
if ($Pscx:Preferences['CD_GetChildItem'])
{
Get-ChildItem
}
elseif ($Pscx:Preferences['CD_EchoNewLocation'])
{
Write-Host $ExecutionContext.SessionState.Path.CurrentLocation
}
}
}
}
Process
{
if ($pscmdlet.ParameterSetName -eq 'Path')
{
Write-Debug "Path parameter received: '$Path'"
$aPath = $Path
}
else
{
Write-Debug "LiteralPath parameter received: '$LiteralPath'"
$aPath = $LiteralPath
}
if ($UnboundArguments -and $UnboundArguments.Count -gt 0)
{
$OFS=','
Write-Debug "Appending unbound arguments to path: '$UnboundArguments'"
$aPath = $aPath " " ($UnboundArguments -join ' ')
}
# If no input, dump contents of backward and foreward stacks
if (!$aPath)
{
# Command to dump the backward amp; foreward stacks
""
" # Directory Stack:"
" --- ----------------"
if ($backwardStack.Count -ge 0)
{
for ($i = 0; $i -lt $backwardStack.Count; $i )
{
" {0,3} {1}" -f $i, $backwardStack[$i]
}
}
"-> {0,3} {1}" -f $i ,$ExecutionContext.SessionState.Path.CurrentLocation
if ($forewardStack.Count -ge 0)
{
$ndx = $i
for ($i = 0; $i -lt $forewardStack.Count; $i )
{
" {0,3} {1}" -f ($ndx $i), $forewardStack[$i]
}
}
""
return
}
Write-Debug "Processing arg: '$aPath'"
$currentPathInfo = $ExecutionContext.SessionState.Path.CurrentLocation
# Expand ..[.] out to ....[..]
if ($aPath -like "*...*")
{
$regex = [regex]"..."
while ($regex.IsMatch($aPath))
{
$aPath = $regex.Replace($aPath, "..$([System.IO.Path]::DirectorySeparatorChar)..")
}
}
if ($aPath -eq "-")
{
if ($backwardStack.Count -eq 0)
{
Write-Warning $msgTbl.BackStackEmpty
}
else
{
$lastNdx = $backwardStack.Count - 1
$prevPath = $backwardStack[$lastNdx]
SetLocationImpl $prevPath -IsLiteralPath
[void]$forewardStack.Insert(0, $currentPathInfo.Path)
$backwardStack.RemoveAt($lastNdx)
}
}
elseif ($aPath -eq " ")
{
if ($forewardStack.Count -eq 0)
{
Write-Warning $msgTbl.ForeStackEmpty
}
else
{
$nextPath = $forewardStack[0]
SetLocationImpl $nextPath -IsLiteralPath
[void]$backwardStack.Add($currentPathInfo.Path)
$forewardStack.RemoveAt(0)
}
}
elseif ($aPath -like "-[0-9]*")
{
[int]$num = $aPath.replace("-","")
$backstackSize = $backwardStack.Count
$forestackSize = $forewardStack.Count
if ($num -eq $backstackSize)
{
Write-Host "`n$($msgTbl.GoingToTheSameDir)`n"
}
elseif ($num -lt $backstackSize)
{
$selectedPath = $backwardStack[$num]
SetLocationImpl $selectedPath -IsLiteralPath
[void]$forewardStack.Insert(0, $currentPathInfo.Path)
$backwardStack.RemoveAt($num)
[int]$ndx = $num
[int]$count = $backwardStack.Count - $ndx
if ($count -gt 0)
{
$itemsToMove = $backwardStack.GetRange($ndx, $count)
$forewardStack.InsertRange(0, $itemsToMove)
$backwardStack.RemoveRange($ndx, $count)
}
}
elseif (($num -gt $backstackSize) -and ($num -lt ($backstackSize 1 $forestackSize)))
{
[int]$ndx = $num - ($backstackSize 1)
$selectedPath = $forewardStack[$ndx]
SetLocationImpl $selectedPath -IsLiteralPath
[void]$backwardStack.Add($currentPathInfo.Path)
$forewardStack.RemoveAt($ndx)
[int]$count = $ndx
if ($count -gt 0)
{
$itemsToMove = $forewardStack.GetRange(0, $count)
$backwardStack.InsertRange(($backwardStack.Count), $itemsToMove)
$forewardStack.RemoveRange(0, $count)
}
}
else
{
Write-Warning ($msgTbl.NumOutOfRangeF1 -f $num)
}
}
else
{
$driveName = ''
if ($ExecutionContext.SessionState.Path.IsPSAbsolute($aPath, [ref]$driveName) -and
!(Test-Path -LiteralPath $aPath -PathType Container))
{
# File or a non-existant path - handle the case of "cd $profile" when the profile script doesn't exist
$aPath = Split-Path $aPath -Parent
Write-Debug "Path is not a container, attempting to set location to parent: '$aPath'"
}
SetLocationImpl $aPath
$forewardStack.Clear()
# Don't add the same path twice in a row
if ($backwardStack.Count -gt 0)
{
$newPathInfo = $ExecutionContext.SessionState.Path.CurrentLocation
if (($currentPathInfo.Provider -eq $newPathInfo.Provider) -and
($currentPathInfo.ProviderPath -eq $newPathInfo.ProviderPath))
{
return
}
}
[void]$backwardStack.Add($currentPathInfo.Path)
}
}
}
Комментарии:
1. Можете ли вы более подробно объяснить последнюю проблему? Как воспроизвести?
2. # 3 Если вы это сделаете:
cd A; cd B; cd C; cd D;
и затем вы делаетеcd -1
, чтобы попасть в каталог «B» (индекс 1). Если вы сейчас это сделаетеcd F
, все предыдущие индексы для C и D будут удалены, а «F» будет добавлен после «B». Я хочу сохранить предыдущие C и D и добавить F после.
Ответ №1:
Единственное изменение, необходимое для объединения данных из Messages.psd1
в определение функции, находится в этой строке в Begin
блоке:
Import-LocalizedData -BindingVariable msgTbl -FileName Messages
Этот оператор будет импортировать ресурсы из файла, зависящего от локали Messages.psd1
, во время выполнения и назначать их $msgTbl
, поэтому нам нужно заменить его статическими значениями:
$msgTbl = ConvertFrom-StringData @'
SettingLocationF1=Setting location to: '{0}'
BackStackEmpty=The backward stack is empty.
ForeStackEmpty=The foreward stack is empty.
GoingToTheSameDir=Wherever you go, there you are!
NumOutOfRangeF1={0} is out of range.
'@
Ошибка, с которой вы столкнулись, легко исправить — поскольку вам просто нужна автономная функция, вам не нужно беспокоиться о параметрах конфигурации, специфичных для Pscx
модуля, поэтому просто удалите это else
утверждение полностью:
else
{
# If not passing thru, then check for user options of other info to display.
if ($Pscx:Preferences['CD_GetChildItem'])
{
Get-ChildItem
}
elseif ($Pscx:Preferences['CD_EchoNewLocation'])
{
Write-Host $ExecutionContext.SessionState.Path.CurrentLocation
}
}
Боюсь, я не понимаю последнего вопроса. С удовольствием обновлю ответ, если вы можете показать, как воспроизвести описанное поведение
Комментарии:
1. Спасибо. Это отлично сработало. Последний пункт см. В Моем объяснении в комментарии выше.