Using PowerShell for everyday work?
|
On our sister site, Server IQ, I've written about PowerShell. I also wrote about it elsewhere before joining Ziff-Davis. It really is a great tool, and I think developers could benefit from using it for their command-line work. Do I use it? Nope. I admit it. I don't use it. Why? Because I've been using the good old DOS prompt for years, getting used to PowerShell seems like having to learn a new language. However, the truth is, PowerShell can do far more than the DOS command, and I really could benefit from it. And so I've decided to start using it, and sharing my adventures here on the blog. Today, while preparing an article for publication on DevSource, I had to rename several files from ending with .jpg to _large.jpg. For example, the filename image1.jpg needed to be renamed image1_large.jpg. I had all the files in a single directory, but try as I might, I couldn't get them renamed correctly with the DOS prompt. Here's an example. (These aren't the actual files; I already renamed the real ones and didn't want to go through the headache of doing it again for this example. So I created four fake files and gave them .jpg names.) Following is my adventure with the DOS command line (incidentally, in the real version I had far, far more than just four files!): C:\temp\jpg > dir Volume in drive C has no label. Volume Serial Number is 48A7-ADD3 Directory of C:\temp\jpg 06/13/2007 11:34 AM <DIR> . 06/13/2007 11:34 AM <DIR> .. 06/07/2007 02:30 PM 4 abc.jpg 06/07/2007 02:30 PM 4 abc123456.jpg 06/07/2007 02:30 PM 4 abcdef.jpg 06/07/2007 02:30 PM 4 picture1000.jpg 4 File(s) 16 bytes 2 Dir(s) 2,633,523,200 bytes free C:\temp\jpg > ren *.jpg *_large.jpg C:\temp\jpg > dir Volume in drive C has no label. Volume Serial Number is 48A7-ADD3 Directory of C:\temp\jpg 06/13/2007 11:41 AM <DIR> . 06/13/2007 11:41 AM <DIR> .. 06/07/2007 02:30 PM 4 abc.jpg_large.jpg 06/07/2007 02:30 PM 4 abc123456.jpg_large.jpg 06/07/2007 02:30 PM 4 abcdef.jpg_large.jpg 06/07/2007 02:30 PM 4 picture1000.jpg_large.jpg 4 File(s) 16 bytes 2 Dir(s) 2,636,660,736 bytes free C:\temp\jpg > See what happened? The files didn't get renamed correctly. After this, I tried fixing it by typing ren *.jpg_large.jpg *_large.jpg, but then no changes happened and they were still wrong. (For the current editing project, I ended up using Windows Explorer and manually renaming each of them by slowly double-clicking each name, and then typing _large over and over. Sure, it's taking me longer to write this blog and pull my remaining hair out over it, but I can't resist a good challenge that results in a life-changing event, like ditching the DOS prompt after some 27 years!) I suspected Powershell could probably do the job. Let's try it out. First, the same command I issued before didn't work (even though PS knows "dir" and "ren" through aliases): PS C:\temp\jpg> dir Directory: Microsoft.PowerShell.Core\FileSystem::C:\temp\jpg Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 6/7/2007 2:30 PM 4 abc.jpg -a--- 6/7/2007 2:30 PM 4 abc123456.jpg -a--- 6/7/2007 2:30 PM 4 abcdef.jpg -a--- 6/7/2007 2:30 PM 4 picture1000.jpg PS C:\temp\jpg> ren *.jpg *_large.jpg Rename-Item : Cannot process argument because the value of argument "path" is invalid. Change the value of the "path" argument and run the operation again. At line:1 char:4 + ren <<<< *.jpg *_large.jpg PS C:\temp\jpg> "path" refers to the first parameter. The second parameter is called "newName". Thus, in actual PowerShell language, the preceding command would be this, although it'll still fail with the same error message: Rename-Item -path *.jpg -newName *_large.jpg To make this actually work, we need to step back and think about the PowerShell model. PowerShell uses a powerful piping mechanism, and in general items are piped one item at a time. Thus, instead of a command doing several operations, we need to pipe the individual items into the command. Rename-Item operates on a single file, and so we need to pipe into it all the files we want. The dir alias is an alias for the Get-ChildItem command. Get-ChildItem gathers up all the files that match a set of criteria, and pushes them out through the pipe one by one. That's just what we need. This command will gather the needed files: Get-ChildItem -path *.jpg or, if you prefer aliases and shorthand, you can type: dir *.jpg just like the good old days. (Remember, in PS, command names are singular, even if they spew out multiple items. Thus the command here is Get-ChildItem, not Get-Children or something.) By itself, this command just pipes everything to the shell itself, and the items get written out to the screen using a default formatter. Instead, we want to pipe the individual items into the Rename-Item command using | Rename-Item. The Rename-Item command, when acting as a pipe can take a single parameter, -newName, and will rename the incoming item with the new name. But we want the new name to be based on the incoming item's current name. Specifically, we want to replace the .jpg portion of the incoming string with _large.jpg. Remember, we're in .NET here. The item piped into Rename-Item is not just a string like old DOS would do; it's a full .NET object with members. The objects dished out by Get-ChildItem are instances of System.IO.FileInfo. (Cool!) If you open the online help, you can see that System.IO.FileInfo has a member called Name, which is an instance of class String. We can access the incoming object using $_ (which is similar to other scripting languages, particular Perl). That's a System.IO.FileInfo instance. The name, then, can be accessed through $_.Name. That's a String, and the String class has a member function called Replace, which takes two parameters, what to replace, and what to replace it with. Thus, to get the new name of the file we can use $_.name.replace(".jpg","_large.jpg"). To use this code, though, we need to put it inside curly braces. Thus, here's the portion of the command that does the renaming: rename-item -NewName {$_.name.replace(".jpg","_large.jpg")} The part inside braces takes the old name as a string, and builds a new string, with .jpg replaced by _large.jpg. That new string is sent into the -NewName parameter. Here, then is the entire command as I tried it (the first two lines are actually a single line; I broke them up here so it would fit the blog): PS C:\temp\jpg> get-childitem -path *.jpg | rename-item -NewName {$_.name.replace(".jpg","_large.jpg")} PS C:\temp\jpg> dir Directory: Microsoft.PowerShell.Core\FileSystem::C:\temp\jpg Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 6/7/2007 2:30 PM 4 abc123456_large.jpg -a--- 6/7/2007 2:30 PM 4 abcdef_large.jpg -a--- 6/7/2007 2:30 PM 4 abc_large.jpg -a--- 6/7/2007 2:30 PM 4 picture1000_large.jpg PS C:\temp\jpg> Look at the resulting filenames. It worked! Of course, that's a whole lot more to type than just ren *.jpg *_large.jpg, but unlike the other, it actually worked. Since I'll be using this on a regular basis, instead of typing it every time, I'll just save it as a custom function. The following code shows you how to do that, and then shows me using the function. (As before, the first two lines are a single line.) PS C:\temp\jpg> Function Rename-JPG { get-childitem -path *.jpg | rename-item -NewName {$_.name.replace(".jpg","_large.jpg")} } PS C:\temp\jpg> cd ../morejpg PS C:\temp\morejpg> Rename-JPG PS C:\temp\morejpg> dir Directory: Microsoft.PowerShell.Core\FileSystem::C:\temp\morejpg Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 6/7/2007 2:30 PM 4 abc123456_large.jpg -a--- 6/7/2007 2:30 PM 4 abcdef_large.jpg -a--- 6/7/2007 2:30 PM 4 abc_large.jpg -a--- 6/7/2007 2:30 PM 4 picture1000_large.jpg PS C:\temp\morejpg> It worked! Now I just go to the directory containing my files, and I type Rename-JPG. (Notice I gave the function a Verb-Noun format to keep in line with the PowerShell standards.) Unfortunately, just typing in the function like I did here won't save it for next time I start up PowerShell. Instead I need to save it to my profile. I won't go into details here; instead you can read about it here. Then, you'll need to see how to sign your scripts. Here's a good place to learn about that.
|


Comments (2)
Also with the command shell it is an easy job:
for %A in (*.jpg) do ren %A %~nA_large.jpg
Posted by Sir Patrick | June 15, 2007 2:50 AM
Cool, thanks for pointing that out.
The only problem is that the for construct in the command shell doesn't gather the files all at once into a collection and then operate on each member of the collection. Instead, it reads the next file name after each iteration. This can cause problems. Check out what happened when I tried it on my sample files:
==========
Directory of C:\temp\morejpg
06/15/2007 08:49 AM .
06/15/2007 08:49 AM ..
06/07/2007 02:30 PM 4 abc.jpg
06/07/2007 02:30 PM 4 abc123456.jpg
06/07/2007 02:30 PM 4 abcdef.jpg
06/07/2007 02:30 PM 4 picture1000.jpg
4 File(s) 16 bytes
2 Dir(s) 2,745,352,192 bytes free
C:\temp\morejpg > for %A in (*.jpg) do ren %A %~nA_large.jpg
C:\temp\morejpg > ren abc.jpg abc_large.jpg
C:\temp\morejpg > ren abc123456.jpg abc123456_large.jpg
C:\temp\morejpg > ren abcdef.jpg abcdef_large.jpg
C:\temp\morejpg > ren abc_large.jpg abc_large_large.jpg
C:\temp\morejpg > ren picture1000.jpg picture1000_large.jpg
C:\temp\morejpg > dir
Volume in drive C has no label.
Volume Serial Number is 48A7-ADD3
Directory of C:\temp\morejpg
06/15/2007 08:49 AM .
06/15/2007 08:49 AM ..
06/07/2007 02:30 PM 4 abc123456_large.jpg
06/07/2007 02:30 PM 4 abcdef_large.jpg
06/07/2007 02:30 PM 4 abc_large_large.jpg
06/07/2007 02:30 PM 4 picture1000_large.jpg
4 File(s) 16 bytes
2 Dir(s) 2,745,352,192 bytes free
C:\temp\morejpg >
==========
There were four files, but five renames took place. The first file, abc.jpg, got renamed to abc_large.jpg. Then the for loop continued in alphanumeric order, and then found the first file again, now called abc_large.jpg, and renamed it a second time to get abc_large_large.jpg.
There are probably some ways around this, but frankly, I'd like to become comfortable with PowerShell simply because the piping and processing features involve objects, not just strings and text.
But I'm sure other people might like to stay working in the command shell instead.
Posted by Jeff Cogswell | June 15, 2007 8:55 AM