Reputation: 171
I was used to a few command line tricks in Windows that increased my productivity a lot.
Now I am told that I should move to PowerShell because it's more POWERful. It took me a while to get a little bit hang of it (objects, piping, etc.), and there are a lot of great tutorials on how to get a few things done. However, some (relatively) basic trick still puzzle me. For instance, what is the equivalent of the FOR structure in PowerShell?
For example,
FOR %i IN (*.jpg) DO Convert %i -resize 800x300 resized/%i
The above line takes all of photos in a folder and uses the ImageMagick's Convert tool to resize the images and restores the resized imaged in a sub-folder called RESIZED.
In PowerShell I tried the command:
Dir ./ | foreach {convert $_.name -resize 800x300 resized/$_name}
This can't work despite all of the googling around I did. What is missing?
Upvotes: 1
Views: 289
Reputation: 18940
Revised based on comments given below
There are many applications with the name "Convert". If I do
Get-Command Convert
on my computer. It shows me an app that is part of the Windows system. If PowerShell is running the wrong app on you, it's never going to work.
The solution will be to point PowerShell at the convert tool inside the ImageMagick program folder. A Google search on "ImageMagick PowerShell" will lead you to lots of people who have faced the same problem as you.
Upvotes: 0
Reputation: 72171
Use:
Get-ChildItem | foreach {convert $_.name -resize 800x300 resized/$($_.name)}
Or, perhaps, you need to pass the full name (with path), also showing a shorter syntax (using aliases):
gci | % {convert $_.fullname -resize 800x300 resized/$($_.name)}
Also, you might want to supply the full path to the executable.
Upvotes: 0
Reputation: 437197
Note that /
rather than \
is used as the path separator in this answer, which works on Windows too and makes the code compatible with the cross-platform PowerShell Core editions.
tl;dr:
$convertExe = './convert' # adjust path as necessary
Get-ChildItem -File -Filter *.jpg | ForEach-Object {
& $convertExe $_.Name -resize 800x300 resized/$($_.Name)
}
Read on for an explanation and background information.
The equivalent of:
FOR %i IN (*.jpg)
is:
Get-ChildItem -File -Filter *.jpg
or, with PowerShell's own wildcard expressions (slower, but more powerful):
Get-ChildItem -File -Path *.jpg # specifying parameter name -Path is optional
If you're not worried about restricting matches to files (as opposed to directories), Get-Item *.jpg
will do too.
While dir
works as a built-in alias for Get-ChildItem
, I recommend getting used to PowerShell's own aliases, which follow a consistent naming convention; e.g., PowerShell's own alias for Get-ChildItem
is gci
Also, in scripts it is better to always use the full command names - both for readability and robustness.
As you've discovered, to process the matching files in a loop you must pipe (|
) the Get-ChildItem
command's output to the ForEach-Object
cmdlet, to which you pass a script block ({ ... }
) that is executed for each input object, and in which $_
refers to the input object at hand.
(foreach
is a built-in alias for ForEach-Object
, but note that there's also a foreach
statement, which works differently, and it's important not to confuse the two.)
There are 2 pitfalls for someone coming from the world of cmd.exe
(batch files):
In PowerShell, referring to an executable by filename only (e.g., convert
) does not execute an executable by that name located in the current directory, for security reasons.
Only executables in the PATH can be executed by filename only, and unless you've specifically placed ImageMagick's convert.exe
in a directory that comes before the SYSTEM32 directory in the PATH, the standard Windows convert.exe
utility (whose purpose is to convert FAT disk volumes to NTFS) will be invoked.
Use Get-Command convert
to see what will actually execute when you submit convert
; $env:PATH
shows the current value of the PATH environment variable (equivalent of echo %PATH%
).
If your custom convert.exe
is indeed in the current directory, invoke it as ./convert
- i.e., you must explicitly reference its location.
Otherwise (your convert.exe
is either not in the PATH at all or is shadowed by a different utility) specify the path to the executable as needed, but note that if you reference that path in a variable or use a string that is single- or double-quoted (which is necessary if the path contains spaces, for instance), you must invoke with &
, the call operator; e.g.,
& $convertExe ...
or & "$HOME/ImageMagic 2/convert" ...
PowerShell sends objects through the pipeline, not strings (this innovation is at the heart of PowerShell's power). When you reference and object's property or an element by index as part of a larger string, you must enclose the expression in $(...)
, the subexpression operator:
resized/$($_.Name)
- Correct: property reference enclosed in $(...)
resized/$_.Name
- !! INCORRECT - $_
is stringified on its own, followed by literal .Name
$(...)
; e.g., $_.Name
by itself, as used in the command in the question, does work, and retains its original type (is not stringified).$_
by itself - does not need $(...)
, but in the case at hand $_
would expand to the full path. For the most part, unquoted tokens that include variable references are treated like implicitly double-quoted strings, whose interpolation rules are summarized in this answer of mine; however, many additional factors come into play, which are summarized here; edge cases are highlighted in this question."resized/$($_.Name)"
- SAFESTUpvotes: 2