I am using Powershell, I loop through files and create a folder with each file's name to put some data there.
$files = @("[som]video.mkv")
$tmp_location = "."
# to reproduce just do it on files with a filename like [somen_id]restofname.ext
foreach ($file in $files){
$base_input = ([io.fileinfo]$file).basename
# base input may be a file called: [somen_id]restofname.ext
$tmp_dir = "$tmp_location/$base_input"
mkdir $tmp_dir # this line works and the directory is created
# do some stuff first before cd
cd $tmp_dir #this does not work
}
cd
fails to handle the tmp_dir variable when it has special characters like []
, but mkdir
(and even rm
) create/delete that directory just fine, which is a very inconsistent behavior in Powershell, I would expect it to either fail for all or work for all!
Any idea how to escape the variable such that it becomes readable to cd
(ofc in real life my array is not just 1 filename written by hand, but this example shows the error too)
Thanks
CodePudding user response:
tl;dr
Use the -LiteralPath
parameter to pass a path meant to be interpreted literally (verbatim) to the Set-Location
cmdlet, which cd
is a built-in alias of; by default (via the positionally implied -Path
parameter), it is interpreted as a wildcard expression:
# * "cd" is a built-in alias of "Set-Location"
# * "sl" is the preferable, PowerShell-idiomatic built-in alias
# * Interactively, using PowerShell's elastic syntax,
# you can shorten "-LiteralPath" to "-l", given that no other parameter name
# (currently) starts with "l"
# * In PowerShell (Core) 7 , "-lp" is an official alias.
cd -LiteralPath $tmp_dir
The same applies analogously to rm
(unlike what your question implies), which is a built-in alias of Remove-Item
.
By contrast, mkdir
(which is a wrapper function for New-Item -ItemType Directory
) implicitly treats its argument literally.
Read on for details.
As for what you tried:
While cd
and mkdir
look like their cmd.exe
counterparts, they are not:
cd
is a built-in alias of theSet-Location
cmdlet.mkdir
is a built-in wrapper function for theNew-Item
cmdlet, with implicitly applied argument-ItemType Directory
.
(On Unix-like platforms,mkdir
isn't an alias at all and instead refers to the external/bin/mkdir
utility).
To learn what command a given name (ultimately) refers to in PowerShell, use the Get-Command
cmdlet; e.g. Get-Command cd
)
Thus,
cd $tmp_dir
is equivalent to the following call, given that Set-Location
binds a positional argument (one not preceded by the target parameter name) to its -Path
parameter:
Set-Location -Path $tmp_dir
Most PowerShell cmdlets interpret -Path
arguments as wildcard expressions, including - perhaps surprisingly - Set-Location
.[1]
Typically, the distinction between -Path
and -LiteralPath
doesn't matter, given that *
and ?
- the usual wildcard characters - aren't even allowed in file and directory names (at least on Windows).
However, the problem is that in PowerShell's wildcard language [
and ]
also have special meaning (they form character sets - e.g. [abc]
and/or character ranges - e.g. [a-c]
), which conflicts with literal use of [
and ]
in file names, and necessitates the use of -LiteralPath
for disambiguation.[2]
By contrast, New-Item
's (possibly positionally implied) -Path
parameter acts like -LiteralPath
, because interpreting a path argument as a wildcard expression in the context of creating a file or directory is pointless. That's why you had no problem creating a directory whose path literally contains [
and ]
with mkdir
.[3]
Why aliases named for a different shell's commands - such as cd
and mkdir
- are best avoided in PowerShell:
In a problematic attempt to ease the migration pain for cmd.exe
users (and in part also for users of POSIX-compatible shells), PowerShell decided to define built-in aliases and wrapper functions that are named for cmd.exe
's internal commands, whereas PowerShell's analogous internal commands, the so-called cmdlets, have very different names.
The latter isn't problematic per se - except that by their use you're missing out on the benefits of PowerShell's standard verb-noun naming convention (e.g., Set-Location
), which also extends to how aliases are formed, given that the approved verbs have official alias forms (e.g., s
for Set-
; therefore, sl
is another, but PowerShell-idiomatic Set-Location
alias).
What is problematic, however, is that PowerShell commands have very different syntax from cmd.exe
's.
If you use names such as cd
and mkdir
, you'll be tempted to think that, e.g. cd
and mkdir
function the same way as in cmd.exe
- which is only true in the most basic of use cases, however.
It's best to use the true PowerShell command names or - for brevity in interactive use - their PowerShell aliases, which are (reasonably) predictably formed, as discussed above (e.g., sl
for Set-Location
and ni
for New-Item
)
[1] By contrast, cmd.exe
's cd
command does not accept wildcards. With Set-Location
, wildcard support is of conceptual necessity limited, because the wildcard expression must resolve to exactly one matching directory.
[2] Alternatively, with -Path
you can escape [
and ]
as `[
and `]
, respectively, but this kind of escaping doesn't work consistently as of PowerShell 7.3.0 - see GitHub issue #7999.
[3] This parameter-naming inconsistency is unfortunate; arguably, -LiteralPath
should at least be supported as a parameter alias name for -Path
. That said, as of PowerShell 7.3.0, there is actually a case where -Path
currently is interpreted as a wildcard expression, namely when combined with the -Name
parameter, but this should be considered a bug - see GitHub issue #17106.