Once again, looking at the interestingness, curiosity and other things on Habré, I came across an
article about how to use Python to clean up the order among the illustrations, which have a digital cemetery in almost all of us. Since not so long ago, I had a share in image processing using PowerShell, I decided to make an illustrative comparison. Indicative from the point of view to demonstrate some of the characteristic features of PowerShell to those who are not yet familiar with it or are only superficially familiar.
Unfortunately, there is a strange situation with PowerShell, when a very powerful tool turns out to be overlooked by the public and definitely needs some popularization. Especially since recently it has been included in Windows 7 and will soon be in workplaces with a considerable number of users. And then there is such an excuse in the form of a laconic one, on the one hand, but interesting, on the other hand, of an administrative task to restore order in the information repositories. So let's get started.
I'll start with a small lyrical digression. Even when you talk about such seemingly simple tools as command processors, you want something sublime. And I think I found it. You will laugh, but I have defined for myself the development under command processors as multi-paradigm. The first paradigm is imperative. We see it in almost all batch files and saw it in the example of the original Python problem. Another paradigm is functional. I called it that because of its similarity to the approach used in functional programming languages. In everyday life, we know it as a command pipe, just a pipe and many other gentle terms :) In short, let me remind you how it looks, in a simple example:
')
X:\> (dir /b folder1\*.txt && dir /b folder2\*.txt) | find "text" | sort
Here we see three instructions, separated by a vertical bar. Each subsequent takes the results of the previous one, performs on them certain operations and sends the next command in the queue pipe. In our example, the standard cmd.exe collects a list of text files from two folders using the first instruction. This list is passed to the find command, which leaves only those strings that contain the substring "text" and are already sent to the sort command, which sorts them. In functional programming languages, this might look like this:
sort(find((dir /b folder1\*.txt && dir /b folder2\*.txt), "text"))
Is there any similarities? In essence, every element of the pipe is akin to a function. Just to the existing types of function entries (such as postfix, prefix and infix entries) was added one more - pipe record :)
Despite the fact that the command pipes, as can be seen from the example, have been since the days of MS-DOS, I would like to separately thank the UNIX-community, which sounds strange, considering the origin of PowerShell in the Microsoft hubs. But there is a simple explanation. It is in UNIX-like systems that these mechanisms were elevated to the rank of art, allowing you to combine different teams into the most unusual and very useful combinations.
It so happened that PowerShell got this one, in my opinion, a very good feature and combined it with another equally successful solution. Unlike the transfer of strings in the command pipe of the same Linux, PowerShell operates with objects. To make everything clearer, I propose to proceed with the implementation of the task. We will do this, of course, using the "functional" approach through the pipe, for the imperative approach would differ little from what we already have in the case of implementation on Python. And you want to compare not only the tools, but also paradigms.
By virtue of the functionality of our approach, we divide the solution of the problem into some similarity of these very functions, and in our case, instructions, each of which performs some strictly defined role. Separately, I’ll note that I’ll intentionally complicate the implementation a bit to show more PowerShell features. In reality, something can be simplified, but something to get rid of.
Step 0 . To begin, we describe some of the conditions of execution in general. First of all, we are interested in input parameters and this is a good reason to consider working with variables in PowerShell.
PS X:\> $source="x:\folder\source"
PS X:\> $target="x:\folder\target"
PS X:\> $source, $target
x:\folder\source
x:\folder\target
In the first line of the $ source variable, we defined as the value the source folder in which the pictures are located for further sorting. We will place the sorted images in the destination folder $ target. The third line simply says that it is necessary to output the values of these variables to the console, which we see below. Note that values are not just quoted. The fact is that the values of variables are typed and so we defined them as strings. In the absence of quotes, the processor will treat the text as a command and assign the variable the results of its execution. For example:
PS X:\> $test=dir x:\folder\source
PS X:\> $test.Length
10
PS X:\> $test[0].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True DirectoryInfo System.IO.FileSystemInfo
As a result of this command, the variable $ test will be a collection of objects in the specified folder. The Length property that we used in the $ test.Length statement is the number of elements in the collection. But $ test [0]. GetType () displays information about the type of the first element of the collection. As you can see, this is not a simple string, but a certain DirectoryInfo. Be the first to file a file - it would be FileInfo. This is a very important illustration of what I said earlier and the fact that we will actively use it later - PowerShell does not send strings on a pipe, but objects of quite specific types.
The next preparatory step is related to the fact that we will use the type of illustration, which is located in the System.Windows.Forms library, which is not loaded by default. We need to give the PowerShell instruction to load it. For example:
PS X:\> [void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
In general, there is another significant feature of PowerShell here that is the creation of any objects offered by .NET or COM. Their name is legion, but this is a separate topic. In this case, just take this string as given. For this, we assume that the execution environment is ready.
Step 1. After the conditions for the execution of the task are ready, we will begin by forming a list of files that pretend to be pictures. The formulation of the problem is precisely this, for we proceed from the assumption that the file extension is useful information, but does not guarantee that the file is a picture. In most cases, you will not need it, but as I said earlier - the decision is intentionally complicated. So, we select all the files of the specified extensions in the specified folder and all its subfolders. Our first step of the pipe will look like this:
PS X:\> dir $source -r -include *.jpg, *.png, *.gif
The option "-r" means recursive directory traversal, in "-include" you can list the masks of the included files (or enumerate maxi excluded in the option "-exclude"). In response to this command, we will get a list of files.
Step 2. The next element of the pipe we are trying to create for each file received from the previous instruction, is a raster image object. This manual serves as an illustration to several PowerShell capabilities at once, but for clarity, let's begin with an example:
PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue
The first thing we see in the second instruction of the pipe is the select command. Its purpose is to form new objects and pass them on. To do this, select a comma-separated list of all the properties of the new object that interest us. The first is FullName. The property specified in this form means that we take it from the object that we got on the pipe and with the same name and value are transferred to the new object. In our case, we are talking about the FullName property of the FileInfo class, which returns the full path to the file.
The following construction is a bit more complicated. It creates a new property, whose name is passed in Name, and the value in Expression. As a value, we create an instance of the class describing the illustration (System.Drawing.Bitmap), passing it to the constructor the same FullName value with the location of the illustration file. Separately note for yourself the difference in the syntax of accessing the FullName property. The select statement does this in a simplified form. In most other cases, the variable $ _ means an object passed to us by pipe, the property of which we can access through the dot and the name of the property.
If the file we are going to work with is not a raster illustration, then an attempt to create a System.Drawing.Bitmap object would result in an error. In order to ignore these errors, we added the ErrorAction option, which allows them to be ignored. Note that this option is not unique to the select command, but refers to the category of so-called Common Parameters, which you can use in almost any other instructions.
Step 3. Following the results of the previous step, we will get a list of objects, each about two properties: FullName with the full path to the file name and Image with an illustration of the Bitmap class instance. If for any of the files it was not possible to create a class of the raster image, then the Image property will be empty. So we need a step that allows us to filter all objects that are not illustrations. The result of the addition of the new instruction will be as follows:
PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue | where { $_.Image }
Everything is quite simple and concise. We encounter a familiar reference to a property. In this case, the Image property. We meet the new where clause, which allows us to transmit further on the pipe only those objects that satisfy the given condition in it. At the same time we get acquainted with a simple check for empty values. Non-empty we would control the condition! $ _. Image, and to more complex conditions we would attract comparison operations, logical operations, etc. For example - where {$ _. Image.Width -gt 1000 -and $ _. Image.Height -gt 1000} to get all the illustrations whose width and height are greater than 1000.
Step 4. After we have received a list of illustrations, we will create for each of them the name of the folder in which the illustration should be saved. We do it like this:
PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue | where { $_.Image } | select FullName, @{Name="ImageFolder"; Expression={"{0}\{1}x{2}" -f $target, $_.Image.Width, $_.Image.Height}}
You are already familiar with the select command and the formation of a new object; formatting strings is of greater interest here. As you can see from the example, everything starts with the format string, and then the values that will be used during formatting are listed. Increasingly corresponds to the string.Format method from .NET and formatting rules can be found in
MSDN .
Step 5. Looking at the result of the execution of this function, you will see that we already have almost everything we need. Namely, we have the full path to the original illustration all in the same FullName property and the destination path with folders according to the sizes in the new ImageFolder property. There remains a continuous imperative to create a folder and copy / move a file there. To do this, we will use the foreach instruction, which allows you to perform other instructions for each object received in the pipe. It will all look like this together:
PS X:\> dir $source -r -include *.jpg, *.png, *.gif | select FullName, @{Name="Image"; Expression={New-Object System.Drawing.Bitmap $_.FullName}} -ErrorAction SilentlyContinue | where { $_.Image } | select FullName, @{Name="ImageFolder"; Expression={"{0}\{1}x{2}" -f $target, $_.Image.Width, $_.Image.Height}} | foreach {if (-not (test-path $_.ImageFolder)) {md $_.ImageFolder}; copy $_.FullName -destination $_.ImageFolder; $_}
As you can see, there are three instructions in the foreach, separated by a semicolon. The second definitely does not need any lengthy comments, for it is a simple copy. Which, by the way, can be replaced with the move command to move the illustration file. The first instruction is slightly longer, but not much more complicated. In the if conditional construct, we check the absence of a folder with the help of logical negation and the test-path construct. If the folder is missing, then only in this case we create it. The third instruction could have been avoided altogether, but I wanted to show her that foreach is not a terminal instruction in the pipe and after it the processing can be continued. Remember how at the very first step we output the values of variables to the console? So here, the instruction $ _ prints the object that we received on the pipe further into the pipe. Instead, you can output something else. For example, to define some variable and display it, say foreach {... .; $ result = ...; $ result}.
Summing up, these few short lines - there is a little more piled up solution to the problem. Is it good, is it bad - it's hard for me to judge. I guess I'm here and then, including what to add to your piggy bank a bit of your collective wisdom :)
UPD : Thank you so much
amirul for the example in the imperative style, which I was too lazy to do. I hope this will remove some problems with the readability of the code. Although, I will not hide, I want to be understood and functional approach. It is no more difficult, because the intricate-looking line passes elementary decomposition into primitive atoms. It’s just unusual, like LISP’s syntax, for example, is unusual for many of us.
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }
Copy Source | Copy HTML [void][reflection.assembly]::LoadWithPartialName( "System.Windows.Forms" ) $source = "x:\source" $target = "x:\target" foreach ( $file in dir $source -r -inc *.jpg, *.gif, *.png) { try { $image = new -object System.Drawing.Bitmap $file .FullName $targetdir = "{0}\{1}x{2}" -f $target , $image .Width, $image .Height if (!(test-path $targetdir )) { md $targetdir } copy $file $targetdir Write-Host $file -> $targetdir } catch { Write-Host $file " **IS NOT COPIED**" } }