从 powershell 中更改目录 运行 的批处理文件不执行任何操作
Batch file to change directory run from within powershell does nothing
我的 PATH 上有一个小的 "dev.bat" 批处理文件,我 运行 切换到 W:\
中的开发项目目录。这在 CMD 中工作正常,但在 PowerShell(或 PWSH)运行 时却不行。
我没有其他问题运行从 PowerShell 生成 .bat 文件。
PS C:\> type C:\dev.bat
W:
CD W:\dev
PS C:\> dev.bat
me@computer C:\
> W:
me@computer W:\dev
> CD W:\dev
PS C:\> echo "Why did dev.bat not change directory??"
Why did dev.bat not change directory??
PS C:\> W:
PS W:\>
不,cmd /c dev.bat
没有区别。
当从 PowerShell 运行 时,批处理文件总是 运行 在 (cmd.exe
) 子进程中 [1],鉴于 PowerShell 本身不理解批处理语言。
更改子进程中的工作目录仅限于该子进程(及其自己的子进程),对调用进程没有影响; 子进程无法更改 调用 进程的工作目录。
您唯一的选择是:
- 让你的批处理文件 echo(打印)所需的工作目录
- 在 PowerShell 中捕获该路径并将其传递给
Set-Location
如果您不想更改批处理文件,请使用以下解决方法:
Set-Location -LiteralPath (cmd /c 'dev.bat >NUL && cd')
# Or if you want to use the 'cd' alias for Set-Location and
# are confident that path never has "[" characters in it (so that
# it can't be mistaken for a wildcard expression):
cd (cmd /c 'dev.bat >NUL && cd')
如果根本不需要涉及批处理文件,而您只是想要一种方便的方法来创建更改到预定义位置(工作目录)的自定义函数,将$PROFILE
文件中的以下函数:
# Helper function to place in $PROFILE, which generates custom quick-cd
# functions, based on a function name and target directory path.
function New-QuickCD ($Name, $LiteralPath) {
$funcDef = @"
function global:$Name { Push-Location -LiteralPath "$LiteralPath" } # quick-CD function
"@
Invoke-Expression $funcDef # define in current session too
$funcDef >> $PROFILE # append to $PROFILE
}
注:
生成的函数使用 Push-Location
而不是 Set-Location
以便使用 Pop-Location
(popd
) 轻松返回到之前的位置。
为方便起见,生成的函数也在 current 会话中通过 Invoke-Expression
[2][=110= 定义] 在创建时,因此您无需重新加载 (dot-source) $PROFILE
或打开新会话即可调用新生成的函数。
用>>
盲目追加到$PROFILE
意味着如果你重新定义一个函数,新的定义会生效,但是过时的前一个将留在文件中,需要手动清理;在每个生成的函数之后放置的注释 # quick-CD function
旨在促进这一点 - 请参阅底部部分以获得更新旧定义的更复杂版本 New-QuickCD
。
您可以通过多种方式使该功能更加健壮和方便:使参数成为必需的,验证路径的存在(默认情况下),将路径解析为绝对路径 - 再次参见底部。
例如,要创建一个名为 dev
并切换到 W:\dev
的函数,您可以调用:
# Generate function 'dev', which switches to 'W:\dev',
# append it to your $PROFILE file, and also define it in this session:
New-QuickCD dev W:\dev
# Call it:
dev # changes the current location to W:\dev; use 'popd' to return.
更强大、更灵活的New-QuickCD
函数:
在上述版本的基础上改进如下:
- 它使参数成为必需的。
- 验证目标目录路径是否存在。
- 它定义了支持
-PrintOnly
开关的函数,该开关只打印函数的目标目录,而不更改它。
- 先把相对路径解析成绝对路径,这样就可以运行
New-QuickCD foo .
定义一个切换到当前位置绝对路径的函数
- 当您重新定义一个函数时,先前的定义会自动更新:
- 为了启用此功能
$PROFILE
被整体重写,使用 >
重定向运算符。
- 要删除 函数,您仍然必须手动编辑
$PROFILE
。
- 它带有基于评论的帮助; 运行
help New-QuickCD -Examples
,例如
function New-QuickCD {
<#
.SYNOPSIS
Creates a custom quick-CD function.
.DESCRIPTION
Creates a custom quick-CD function and appends it your $PROFILE file.
Such a function changes to a fixed location (directory) stored inside the
function, specified at creation time to allow for quickly changing to
frequently used directories using a short name.
For convenience, a newly created function is also defined for the running
session (not just for all future sessions).
The quick-CD functions use Push-Location to change location, which
enables you to easily return to the previously active location with
Pop-Location (popd).
To determine what location a given quick-CD function *would* change to,
invoke it with the -PrintOnly switch.
.PARAMETER FunctionName
The name of the quick-CD function to define.
.PARAMETER DirectoryPath
The literal path of the directory the quick-CD function should change to.
If given a relative path, it is resolved to an absolute one first.
For convenience, you may specify a *file* path, in which case that file's
parent path is used.
.NOTES
Your $PROFILE file is recreated every time you use this function, using the
> redirection operator, so as to support updating functions in place.
To *remove* a quick-CD function, edit $PROFILE manually.
.EXAMPLE
New-QuickCD dev W:\dev
Adds a 'dev' function to $PROFILE, which on invocation changes the current
location to W:\dev
* Call just 'dev' to change to W:\dev. Use popd to return to the previous
location.
* Call 'dev -PrintOnly' to print what location function 'dev' *would*
change to.
.EXAMPLE
New-QuickCD proj .
Adds a 'proj' function to $PROFILE, which on invocation changes to the
the location that is current at the time of calling New-QuickCd.
#>
param(
[Parameter(Mandatory)] [string] $FunctionName,
[Parameter(Mandatory)] [string] $DirectoryPath
)
Set-StrictMode -Version 1; $ErrorActionPreference = 'Stop'
# Resolve the path to a full path. Fail if it doesn't exist.
$fullPath = (Resolve-Path -ErrorAction Stop -LiteralPath $DirectoryPath).Path
# As a courtesy, if the path is a *file*, we use its parent path instead.
if (Test-Path -PathType Leaf $fullPath) {
$fullPath = [IO.Path]::GetDirectoryName($fullPath)
}
# Define a comment that identifies the functions we add to $PROFILE as
# quick-CD functions.
$idComment = '<# quick-CD function generated with New-QuickCD #>'
# Generate the new function's source code...
# * on a *single line*, which enables easy filtering when updating $PROFILE below
# * with a distinctive comment at the end of the line that identifies the
# function as a quick-CD function.
# * with the global: scope specifier, which makes it easier to call the
# same definition with Invok-Expression to make the function available in the
# current session too.
$newFuncDef = @"
$idComment function global:$FunctionName { param([switch] `$PrintOnly) if (`$PrintOnly) { "$fullPath" } else { Push-Location -LiteralPath "$fullPath" } }
"@
# ... define it in the current session (doing this *before* updating $PROFILE ensures early exit if the function name is invalid)
Invoke-Expression $newFuncDef
# ... and update $PROFILE:
# Get the current content of $PROFILE
[string] $currentProfileContent = if (Test-Path -LiteralPath $PROFILE) { Get-Content -Raw -LiteralPath $PROFILE }
# Try to replace an existing definition.
$newProfileContent = $currentProfileContent -replace ('(?m)^{0} function global:{1} .+$' -f [regex]::Escape($idComment), [regex]::Escape($FunctionName)), $newFuncDef
if (-not $currentProfileContent -or $newProfileContent -ceq $currentProfileContent) { # Profile didn't exist or nothing was replaced -> we must append the new definition.
$newProfileContent = $newProfileContent.TrimEnd() + [Environment]::NewLine * 2 + $newFuncDef
}
# Write the file.
$newProfileContent > $PROFILE
}
[1] 相比之下,批处理文件 运行 进程中 从 cmd.exe
调用时,类似于 PowerShell 运行它的 *.ps1
脚本正在处理中。
POSIX-like shell,例如 Bash,另一方面,默认情况下 运行 它们的脚本在子进程中,除非使用 sourcing (.
, source
)
[2] 虽然这是对 Invoke-Expression
的安全使用,但 should generally be avoided.
@mkelement 是正确的:没有简单的方法可以从路径上的 .bat 文件执行此操作 - 那是老派。正确的 PowerShell 方法是为一个函数创建一个别名来执行您想要的操作。
借鉴this answer我的解决方案是:
第 1 步:创建一个可重复使用的函数来创建别名:
PS> echo 'function myAlias {
$g=[guid]::NewGuid();
$alias = $args[0]; $commands = $args[1];
echo "function G$g { $commands }; New-Alias -Force $alias G$g">>$profile
};'>>$profile
重新启动 powershell(以加载上述功能),然后按如下方式定义您的 dev
快捷方式:
第 2 步:创建一个 dev
shortcut/alias,让您到达想要的位置:
PS> myAlias dev "Set-Location W:\dev"
第三步:开心使用dev
PS C:\> dev
PS W:\dev>
另一种简单的方法是创建一个 dev.ps1(PowerShell 脚本文件)而不是批处理文件,其中包含以下代码 Set-Location -Path "W:\dev"
注意:批处理文件作为子进程在 CMD 进程上运行,即使您在 powershell 上运行它也是如此
我的 PATH 上有一个小的 "dev.bat" 批处理文件,我 运行 切换到 W:\
中的开发项目目录。这在 CMD 中工作正常,但在 PowerShell(或 PWSH)运行 时却不行。
我没有其他问题运行从 PowerShell 生成 .bat 文件。
PS C:\> type C:\dev.bat
W:
CD W:\dev
PS C:\> dev.bat
me@computer C:\
> W:
me@computer W:\dev
> CD W:\dev
PS C:\> echo "Why did dev.bat not change directory??"
Why did dev.bat not change directory??
PS C:\> W:
PS W:\>
不,cmd /c dev.bat
没有区别。
当从 PowerShell 运行 时,批处理文件总是 运行 在 (cmd.exe
) 子进程中 [1],鉴于 PowerShell 本身不理解批处理语言。
更改子进程中的工作目录仅限于该子进程(及其自己的子进程),对调用进程没有影响; 子进程无法更改 调用 进程的工作目录。
您唯一的选择是:
- 让你的批处理文件 echo(打印)所需的工作目录
- 在 PowerShell 中捕获该路径并将其传递给
Set-Location
如果您不想更改批处理文件,请使用以下解决方法:
Set-Location -LiteralPath (cmd /c 'dev.bat >NUL && cd')
# Or if you want to use the 'cd' alias for Set-Location and
# are confident that path never has "[" characters in it (so that
# it can't be mistaken for a wildcard expression):
cd (cmd /c 'dev.bat >NUL && cd')
如果根本不需要涉及批处理文件,而您只是想要一种方便的方法来创建更改到预定义位置(工作目录)的自定义函数,将$PROFILE
文件中的以下函数:
# Helper function to place in $PROFILE, which generates custom quick-cd
# functions, based on a function name and target directory path.
function New-QuickCD ($Name, $LiteralPath) {
$funcDef = @"
function global:$Name { Push-Location -LiteralPath "$LiteralPath" } # quick-CD function
"@
Invoke-Expression $funcDef # define in current session too
$funcDef >> $PROFILE # append to $PROFILE
}
注:
生成的函数使用
Push-Location
而不是Set-Location
以便使用Pop-Location
(popd
) 轻松返回到之前的位置。为方便起见,生成的函数也在 current 会话中通过
Invoke-Expression
[2][=110= 定义] 在创建时,因此您无需重新加载 (dot-source)$PROFILE
或打开新会话即可调用新生成的函数。用
>>
盲目追加到$PROFILE
意味着如果你重新定义一个函数,新的定义会生效,但是过时的前一个将留在文件中,需要手动清理;在每个生成的函数之后放置的注释# quick-CD function
旨在促进这一点 - 请参阅底部部分以获得更新旧定义的更复杂版本New-QuickCD
。您可以通过多种方式使该功能更加健壮和方便:使参数成为必需的,验证路径的存在(默认情况下),将路径解析为绝对路径 - 再次参见底部。
例如,要创建一个名为 dev
并切换到 W:\dev
的函数,您可以调用:
# Generate function 'dev', which switches to 'W:\dev',
# append it to your $PROFILE file, and also define it in this session:
New-QuickCD dev W:\dev
# Call it:
dev # changes the current location to W:\dev; use 'popd' to return.
更强大、更灵活的New-QuickCD
函数:
在上述版本的基础上改进如下:
- 它使参数成为必需的。
- 验证目标目录路径是否存在。
- 它定义了支持
-PrintOnly
开关的函数,该开关只打印函数的目标目录,而不更改它。 - 先把相对路径解析成绝对路径,这样就可以运行
New-QuickCD foo .
定义一个切换到当前位置绝对路径的函数 - 当您重新定义一个函数时,先前的定义会自动更新:
- 为了启用此功能
$PROFILE
被整体重写,使用>
重定向运算符。 - 要删除 函数,您仍然必须手动编辑
$PROFILE
。
- 为了启用此功能
- 它带有基于评论的帮助; 运行
help New-QuickCD -Examples
,例如
function New-QuickCD {
<#
.SYNOPSIS
Creates a custom quick-CD function.
.DESCRIPTION
Creates a custom quick-CD function and appends it your $PROFILE file.
Such a function changes to a fixed location (directory) stored inside the
function, specified at creation time to allow for quickly changing to
frequently used directories using a short name.
For convenience, a newly created function is also defined for the running
session (not just for all future sessions).
The quick-CD functions use Push-Location to change location, which
enables you to easily return to the previously active location with
Pop-Location (popd).
To determine what location a given quick-CD function *would* change to,
invoke it with the -PrintOnly switch.
.PARAMETER FunctionName
The name of the quick-CD function to define.
.PARAMETER DirectoryPath
The literal path of the directory the quick-CD function should change to.
If given a relative path, it is resolved to an absolute one first.
For convenience, you may specify a *file* path, in which case that file's
parent path is used.
.NOTES
Your $PROFILE file is recreated every time you use this function, using the
> redirection operator, so as to support updating functions in place.
To *remove* a quick-CD function, edit $PROFILE manually.
.EXAMPLE
New-QuickCD dev W:\dev
Adds a 'dev' function to $PROFILE, which on invocation changes the current
location to W:\dev
* Call just 'dev' to change to W:\dev. Use popd to return to the previous
location.
* Call 'dev -PrintOnly' to print what location function 'dev' *would*
change to.
.EXAMPLE
New-QuickCD proj .
Adds a 'proj' function to $PROFILE, which on invocation changes to the
the location that is current at the time of calling New-QuickCd.
#>
param(
[Parameter(Mandatory)] [string] $FunctionName,
[Parameter(Mandatory)] [string] $DirectoryPath
)
Set-StrictMode -Version 1; $ErrorActionPreference = 'Stop'
# Resolve the path to a full path. Fail if it doesn't exist.
$fullPath = (Resolve-Path -ErrorAction Stop -LiteralPath $DirectoryPath).Path
# As a courtesy, if the path is a *file*, we use its parent path instead.
if (Test-Path -PathType Leaf $fullPath) {
$fullPath = [IO.Path]::GetDirectoryName($fullPath)
}
# Define a comment that identifies the functions we add to $PROFILE as
# quick-CD functions.
$idComment = '<# quick-CD function generated with New-QuickCD #>'
# Generate the new function's source code...
# * on a *single line*, which enables easy filtering when updating $PROFILE below
# * with a distinctive comment at the end of the line that identifies the
# function as a quick-CD function.
# * with the global: scope specifier, which makes it easier to call the
# same definition with Invok-Expression to make the function available in the
# current session too.
$newFuncDef = @"
$idComment function global:$FunctionName { param([switch] `$PrintOnly) if (`$PrintOnly) { "$fullPath" } else { Push-Location -LiteralPath "$fullPath" } }
"@
# ... define it in the current session (doing this *before* updating $PROFILE ensures early exit if the function name is invalid)
Invoke-Expression $newFuncDef
# ... and update $PROFILE:
# Get the current content of $PROFILE
[string] $currentProfileContent = if (Test-Path -LiteralPath $PROFILE) { Get-Content -Raw -LiteralPath $PROFILE }
# Try to replace an existing definition.
$newProfileContent = $currentProfileContent -replace ('(?m)^{0} function global:{1} .+$' -f [regex]::Escape($idComment), [regex]::Escape($FunctionName)), $newFuncDef
if (-not $currentProfileContent -or $newProfileContent -ceq $currentProfileContent) { # Profile didn't exist or nothing was replaced -> we must append the new definition.
$newProfileContent = $newProfileContent.TrimEnd() + [Environment]::NewLine * 2 + $newFuncDef
}
# Write the file.
$newProfileContent > $PROFILE
}
[1] 相比之下,批处理文件 运行 进程中 从 cmd.exe
调用时,类似于 PowerShell 运行它的 *.ps1
脚本正在处理中。
POSIX-like shell,例如 Bash,另一方面,默认情况下 运行 它们的脚本在子进程中,除非使用 sourcing (.
, source
)
[2] 虽然这是对 Invoke-Expression
的安全使用,但 should generally be avoided.
@mkelement 是正确的:没有简单的方法可以从路径上的 .bat 文件执行此操作 - 那是老派。正确的 PowerShell 方法是为一个函数创建一个别名来执行您想要的操作。
借鉴this answer我的解决方案是:
第 1 步:创建一个可重复使用的函数来创建别名:
PS> echo 'function myAlias {
$g=[guid]::NewGuid();
$alias = $args[0]; $commands = $args[1];
echo "function G$g { $commands }; New-Alias -Force $alias G$g">>$profile
};'>>$profile
重新启动 powershell(以加载上述功能),然后按如下方式定义您的 dev
快捷方式:
第 2 步:创建一个 dev
shortcut/alias,让您到达想要的位置:
PS> myAlias dev "Set-Location W:\dev"
第三步:开心使用dev
PS C:\> dev
PS W:\dev>
另一种简单的方法是创建一个 dev.ps1(PowerShell 脚本文件)而不是批处理文件,其中包含以下代码 Set-Location -Path "W:\dev"
注意:批处理文件作为子进程在 CMD 进程上运行,即使您在 powershell 上运行它也是如此