{"id":5523,"date":"2014-07-17T14:57:14","date_gmt":"2014-07-17T13:57:14","guid":{"rendered":"http:\/\/www.walkingrandomly.com\/?p=5523"},"modified":"2014-09-04T14:10:14","modified_gmt":"2014-09-04T13:10:14","slug":"software-carpentry-draft-of-windows-scripting-tutorial-using-powershell","status":"publish","type":"post","link":"https:\/\/walkingrandomly.com\/?p=5523","title":{"rendered":"Software Carpentry: Draft of Windows Scripting Tutorial using PowerShell"},"content":{"rendered":"<p><strong>Update: September 2014<\/strong> &#8211; The notes in this blog post have been uploaded to github: <a href=\"https:\/\/github.com\/mikecroucher\/Windows_Scientific_Computing\">https:\/\/github.com\/mikecroucher\/Windows_Scientific_Computing<\/a>. The blog post will be kept as-is for posterity reasons. For the most up to date version of the notes, see the github version.<\/p>\n<p>Some time in 2013, I helped out at a <a href=\"http:\/\/software-carpentry.org\/\">Software Carpentry<\/a> event at <a href=\"http:\/\/software-carpentry.org\/blog\/2013\/07\/bath-feedback.html\">The University of Bath<\/a>. \u00a0As with most software carpentry boot camps, one of the topics covered was shell scripting and the scripting language of choice was <a href=\"http:\/\/en.wikipedia.org\/wiki\/Bash_(Unix_shell)\">bash<\/a>. \u00a0As I wandered around the room, I asked the delegates which operating system they use for the majority of their research and the <strong>most popular answer, by far, was Windows.<\/strong><\/p>\n<p>This led me to wonder if we should teach using a native Windows solution rather than relying on bash?<\/p>\n<p>A few years ago, this would be an insane proposition since the Windows command shell is very weak compared to bash. \u00a0<a href=\"http:\/\/en.wikipedia.org\/wiki\/Windows_PowerShell\">PowerShell<\/a>, on the other hand, is modern, powerful and installed on all modern Windows operating systems by default.<\/p>\n<p>My problem was that I didn&#8217;t know PowerShell very well. \u00a0So, I took the notes for the 2013 Bath shell scripting session &#8211;\u00a0<a href=\"https:\/\/github.com\/swcarpentry\/boot-camps\/tree\/2013-07-bath\/shell\">https:\/\/github.com\/swcarpentry\/boot-camps\/tree\/2013-07-bath\/shell<\/a>\u00a0&#8211; and gave myself the exercise of converting them to PowerShell.<\/p>\n<p>I got close to completing this exercise last summer but various things took higher priority and so the project languished. \u00a0Rather than sit on the notes any longer, I&#8217;ve decided to just publish what I have so far in case they are useful to anyone.<\/p>\n<p>You are free to use them with the following caveats<\/p>\n<ul>\n<li>This is not necessarily the right way to teach PowerShell. It is an experiment in converting some classroom-tested Linux based notes to PowerShell.<\/li>\n<li>If you use them, attribution would be nice. I am Mike Croucher, my site is <a href=\"https:\/\/www.walkingrandomly.com\">www.walkingrandomly.com<\/a> Details on how to contact me at <a href=\"https:\/\/www.walkingrandomly.com\/?page_id=2055\">https:\/\/www.walkingrandomly.com\/?page_id=2055<\/a><\/li>\n<li>I have not yet tested these notes in a classroom situation<\/li>\n<li><strong>These notes aren&#8217;t finished yet<\/strong><\/li>\n<li>These notes have been developed and tested on Windows 7. \u00a0Behaviour may be different using different versions of Windows.<\/li>\n<li>These notes are given as they were left sometime in mid 2013. <strong>Some things may be out of date.<\/strong><\/li>\n<li>I was learning PowerShell as I developed these notes. As such, I fully expect them to be full of mistakes. \u00a0Corrections and improvements would be welcomed.<\/li>\n<\/ul>\n<p>If anyone is interested in developing these notes into something that&#8217;s classroom-ready, <a href=\"https:\/\/www.walkingrandomly.com\/?page_id=2055\">contact me<\/a>.<\/p>\n<h2>The old Windows Command Shell<\/h2>\n<p>The traditional Windows command shell is a program called cmd.exe which can trace its roots all the way back to the old, pre-Windows DOS prompt.<\/p>\n<p>You can launch this command shell as follows<\/p>\n<ul>\n<li>Hold down both the Windows button and the letter R to open the Run prompt<\/li>\n<li>Type cmd and press Enter or click OK<\/li>\n<\/ul>\n<p><img decoding=\"async\" alt=\"Launch Cmd\" src=\".\/images\/PS\/launchcmd.png\" \/><\/p>\n<ul>\n<li>You should see a window similar to the one below<\/li>\n<\/ul>\n<p><img decoding=\"async\" alt=\"Windows Command Prompt\" src=\".\/images\/PS\/cmdexe.png\" \/><\/p>\n<p>The Windows command shell hasn\u2019t changed significantly for over twenty years and is relatively feature poor compared to more modern shells. For this reason, it is recommended that you use Windows PowerShell instead. Mention of cmd.exe is only included here since, despite its deficiencies, it is still widely in use<\/p>\n<h2>PowerShell<\/h2>\n<p>To launch PowerShell:<\/p>\n<ul>\n<li>Hold down both the Windows button and the letter R to open the Run prompt<\/li>\n<li>Type <strong>powershell<\/strong> and press Enter or click OK<\/li>\n<\/ul>\n<p><img decoding=\"async\" alt=\"Launch PowerShell\" src=\".\/images\/PS\/launchpowershell.png\" \/><\/p>\n<ul>\n<li>You should see a window similar to the one below<\/li>\n<\/ul>\n<p><img decoding=\"async\" alt=\"PowerShell prompt\" src=\".\/images\/PS\/powershell.png\" \/><\/p>\n<p>Note that although the header of the above window mentions v1.0, it could be a screenshot from either version 1.0 or version 2.0. This is a well-known bug. If you are using Windows 7 you will have version 2 at the minimum.<\/p>\n<h2>PowerShell versions<\/h2>\n<p>At the time of writing, PowerShell is at version 3. Ideally, you should at least have version 2.0 installed. To check version:<\/p>\n<pre><code>$psversiontable.psversion\r\n\r\nMajor  Minor  Build  Revision\r\n-----  -----  -----  --------\r\n3      0      -1     -1\r\n<\/code><\/pre>\n<p>If this variable does not exist, you are probably using version 1.0 and should upgrade.<\/p>\n<p>Version 3.0 is available at <a href=\"http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2013\/06\/02\/weekend-scripter-install-powershell-3-0-on-windows-7.aspx?wa=wsignin1.0\">http:\/\/blogs.technet.com\/b\/heyscriptingguy\/archive\/2013\/06\/02\/weekend-scripter-install-powershell-3-0-on-windows-7.aspx<\/a><\/p>\n<h2>Comments<\/h2>\n<pre><code># This is a comment in Powershell. It is not executed\r\n<\/code><\/pre>\n<h2>Directories<\/h2>\n<p>Users of Bash will feel right at home at first since PowerShell appears to have the same set of commands<\/p>\n<pre><code>pwd                         #Path to current folder\r\nls                          #List directory\r\nls *.txt                    #Wild Card\r\nls *_hai*\r\nls -R                       #Recursive folder listing\r\nls .                        #List current folder\r\nls ..                       #List Parent folder\r\ncd ..                       #Change current folder to parent. (Move up a folder)\r\ncd ~                        #Change current folder to your user directory.\r\nmkdir myfolder              #Create a folder\r\nmkdir ~\/myfolder    \r\nmv myfolder new_myfolder    #rename myfolder to new_myfolder\r\nrm -r new_myfolder          #Delete new_myfolder if its empty\r\n<\/code><\/pre>\n<h1>Files<\/h1>\n<pre><code>cat file                    # View file\r\nmore file                   # Page through file\r\ncat file | select -first 3  # first N lines\r\ncat file | select -last 2   # Last N lines\r\ncp file1 file2              # Copy\r\ncp *.txt directory\r\nrm file.txt                 # Delete - no recycle bin.\r\nrm -r directory             # Recurse\r\n<\/code><\/pre>\n<h2>Different command types in PowerShell: Aliases, Functions and Cmdlets<\/h2>\n<p>Many of the PowerShell &#8216;commands&#8217; we&#8217;ve used so far are actually aliases to <a href=\"http:\/\/technet.microsoft.com\/en-us\/scriptcenter\/dd772285.aspx\">Powershell Cmdlets<\/a> which have a Verb-Noun naming convention. We can discover what each command is an alias of using the <strong>get-alias<\/strong> cmdlet.<\/p>\n<pre><code>PS &gt; get-alias ls\r\n\r\nCommandType     Name                                                Definition\r\n-----------     ----                                                ----------\r\nAlias           ls                                                  Get-ChildItem\r\n<\/code><\/pre>\n<p>This shows that <strong>ls<\/strong> is an alias for the Cmdlet <strong>Get-ChildItem<\/strong><\/p>\n<p>A list of aliases for common Bash commands:<\/p>\n<ul>\n<li>cat (Get-Content)<\/li>\n<li>cd (Set-Location)<\/li>\n<li>ls (Get-ChildItem)<\/li>\n<li>pwd (Get-Location)<\/li>\n<\/ul>\n<p>One reason why aliases were created is to make PowerShell a more familiar environment for users of other shells such as the old Windows cmd.exe or Linux&#8217;s Bash environment and also to save on typing.<\/p>\n<p>You can get a list of all aliases using <strong>get-alias<\/strong> on its own.<\/p>\n<pre><code>PS &gt; get-alias\r\n<\/code><\/pre>\n<p>Finally, here&#8217;s how you get all of the aliases for the <strong>Get-ChildItem<\/strong> cmdlet.<\/p>\n<pre><code>get-alias | where-object {$_.Definition -match \"Get-Childitem\"}\r\n<\/code><\/pre>\n<p>For more details on Powershell aliases, see Microsoft&#8217;s documentation at <a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/ee692685.aspx\">http:\/\/technet.microsoft.com\/en-us\/library\/ee692685.aspx<\/a><\/p>\n<h3>What type of command is mkdir?<\/h3>\n<p>The <strong>mkdir<\/strong> command looks like it might be an alias as well since it doesn&#8217;t have the verb-noun naming convention of Cmdlets. Let&#8217;s try to see which Cmdlet it might be an alias of:<\/p>\n<pre><code>PS &gt; get-alias mkdir\r\n\r\nGet-Alias : This command cannot find a matching alias because alias with name 'mkdir' do not exist. \r\nAt line:1 char:6\r\n+ alias &lt;&lt;&lt;&lt;  mkdir\r\n    + CategoryInfo          : ObjectNotFound: (mkdir:String) [Get-Alias], ItemNotFoundException\r\n    + FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand\r\n<\/code><\/pre>\n<p>It turns out that <strong>mkdir<\/strong> isn&#8217;t an alias at all but is actually yet another PowerShell command type, a function. We can see this by using the <strong>get-command<\/strong> Cmdlet<\/p>\n<pre><code>PS &gt; get-command mkdir\r\nCommandType     Name                                                Definition\r\n-----------     ----                                                ----------\r\nFunction        mkdir                                               ...\r\nApplication     mkdir.exe                                           C:\\Program Files (x86)\\Git\\bin\\mkdir.exe\r\n<\/code><\/pre>\n<p>Now we can clearly see that mkdir is a PowerShell function. The mkdir.exe is an Application which you&#8217;ll only see if you installed <a href=\"http:\/\/msysgit.github.io\/\">git for windows<\/a> as I have.<\/p>\n<h2>Cmdlets<\/h2>\n<p>A Cmdlet (pronounced &#8216;command-let&#8217;) is a .NET class but you don&#8217;t need to worry abut what this means until you get into advanced PowerShell usage. Just think of Cmdlets as the base type of PowerShell command. They are always named according to the convention <strong>verb-noun<\/strong>; for example <strong>Set-Location<\/strong> and <strong>Get-ChildItem<\/strong>.<\/p>\n<h4>Listing all Cmdlets<\/h4>\n<p>The following lists all Cmdlets<\/p>\n<pre><code>Get-Command\r\n<\/code><\/pre>\n<p>You can pipe this list to a pager<\/p>\n<pre><code>Get-Command | more\r\n<\/code><\/pre>\n<h2>Getting help<\/h2>\n<p>You can get help on any PowerShell command using the -? switch. For example<\/p>\n<pre><code>ls -?\r\n<\/code><\/pre>\n<p>When you do this, you&#8217;ll get help for the <strong>Get-ChildItem<\/strong> Cmdlet which would be confusing if you didn&#8217;t know that ls is actually an alias for <strong>Get-ChildItem<\/strong><\/p>\n<h2>History<\/h2>\n<p>Up arrow browses previous commands.<\/p>\n<p>By default, PowerShell version 2 remembers the last 64 commands whereas PowerShell version 3 remembers 4096. This number is controlled by the $MaximumHistoryCount variable<\/p>\n<pre><code>PS &gt; $MaximumHistoryCount           #Display the current value\r\nPS &gt; $MaximumHistoryCount=150       #Change it to 150\r\nPS &gt; history                        #Display recent history using the alias version of the command\r\nPS &gt; get-history                    #Display recent history using the Cmdlet direct\r\n<\/code><\/pre>\n<p>Although it remembers more, PowerShell only shows the last 32 commands by default. To see a different number, use the count switch<\/p>\n<pre><code>PS &gt; get-history -count 50\r\n<\/code><\/pre>\n<p>To run the Nth command in the history use Invoke-History<\/p>\n<pre><code>PS &gt; invoke-history 7\r\n<\/code><\/pre>\n<h2>Word count (and more) using Measure-Object<\/h2>\n<p>Linux has a command called <strong>wc<\/strong> that counts the number of lines and words in a file. Powershell has no such command but we can do something similar with the <strong>Measure-Object<\/strong> Cmdlet.<\/p>\n<p>Say we want to count the number of lines, words and characters in the file foo.txt. The first step is to get the content of the file<\/p>\n<pre><code>get-content foo.txt                 # gets the content of foo.txt\r\n<\/code><\/pre>\n<p>Next, we pipe the result of the get-content Cmdlet to Measure-Object, requesting lines, words and characters<\/p>\n<pre><code>get-content foo.txt | measure-object -line -character -word\r\n<\/code><\/pre>\n<p>The measure-object Cmdlet can also count files<\/p>\n<pre><code>ls *.txt | measure-object           #Counts number of .txt files in the current folder\r\n<\/code><\/pre>\n<p>When you execute the above command, a table of results will be returned:<\/p>\n<pre><code>Count    : 3\r\nAverage  :\r\nSum      :\r\nMaximum  :\r\nMinimum  :\r\nProperty :\r\n<\/code><\/pre>\n<p>This is because the measure-object Cmdlet, like all PowerShell Cmdlets, actually returns an object and the above table is the textual representation of that object.<\/p>\n<p>The fields in this table hint that <strong>measure-object<\/strong> can do a lot more than simply count things. For example, here we find some statistics concerning the file lengths found by the ls *.txt command<\/p>\n<pre><code>ls *.txt | measure-object -property length -minimum -maximum -sum -average\r\n<\/code><\/pre>\n<p>You may wonder exactly what type of object has been returned from measure-object and we can discover this by running the gettype() method of the returned object<\/p>\n<pre><code>(ls *.txt | measure-object).gettype()\r\n<\/code><\/pre>\n<p>Request just the name as follows<\/p>\n<pre><code>(ls *.txt | measure-object).gettype().Name\r\n\r\nGenericMeasureInfo\r\n<\/code><\/pre>\n<p>To find out what properties an object has, pass it to the <strong>get-member<\/strong> Cmdlet<\/p>\n<pre><code>#Return all member types\r\nls *.txt | get-member\r\n\r\n#Return only Properties\r\nls *.txt | get-member -membertype property\r\n<\/code><\/pre>\n<p>Sometimes, you&#8217;ll want to simply return the numerical value of an object&#8217;s property and you do this using the <strong>select-object<\/strong> Cmdlet. Here we ask for just the Count property of the GenericMeasureInfo object returned by <strong>measure-object<\/strong>.<\/p>\n<pre><code>#Counts the number of *.txt files and returns just the numerical result\r\nls *.txt | measure-object | select-object -expand Count\r\n<\/code><\/pre>\n<h2>Searching within files<\/h2>\n<p>The Unix world has <strong>grep<\/strong>, PowerShell has <strong>Select String. \u00a0<\/strong>Try running the following on <a href=\"https:\/\/www.walkingrandomly.com\/images\/PS\/haiku.txt\">haiku.txt<\/a><\/p>\n<pre><code>Select-String the haiku.txt                             #Case insensitive by default, unlike grep\r\nSelect-String the haiku.txt -CaseSensitive              #Behaves more like grep\r\nSelect-String day haiku.txt -CaseSensitive\r\nSelect-String is haiku.txt -CaseSensitive\r\nSelect-String 'it is' haiku.txt -Casesensitive\r\n<\/code><\/pre>\n<p>There is no direct equivalent to grep&#8217;s -w switch.<\/p>\n<pre><code>grep -w is haiku.txt            #exact match\r\n<\/code><\/pre>\n<p>However, you can get the same behaviour using the word boundary anchors, <strong>\\b<\/strong><\/p>\n<pre><code>Select-String \\bis\\b haiku.txt -casesensitive\r\n<\/code><\/pre>\n<p>Grep has a -v switch that shows all lines that do not match a pattern. <strong>Select-String<\/strong> makes use of the <strong>-notmatch<\/strong> switch.<\/p>\n<pre><code>BASH: grep -v \"is\" haiku.txxt\r\nPS: select-string -notmatch \"is\" haiku.txt -CaseSensitive\r\n<\/code><\/pre>\n<p>Grep has an -r switch which stands for &#8216;recursive&#8217;. The following will search through all files and subfolders of your current directory, looking for files that contain <strong>is<\/strong><\/p>\n<pre><code>grep -r is *\r\n<\/code><\/pre>\n<p><strong>Select-String<\/strong> has no direct equivalent to this. However, you can do the same thing by using get-childitem to get the list of files, piping the output to <strong>select-string<\/strong><\/p>\n<pre><code>get-childitem * -recurse | select-string is\r\n<\/code><\/pre>\n<p>One difference between <strong>grep<\/strong> and <strong>Select-String<\/strong> is that the latter includes the filename and line number of each match.<\/p>\n<pre><code>grep the haiku.txt\r\n\r\nIs not the true Tao, until\r\nand the presence of absence:\r\n\r\nSelect-String the haiku.txt -CaseSensitive\r\n\r\nhaiku.txt:2:Is not the true Tao, until\r\nhaiku.txt:6:and the presence of absence:  \r\n<\/code><\/pre>\n<p>To get the <strong>grep<\/strong>-like output, use the following<\/p>\n<pre><code>Select-String the haiku.txt -CaseSensitive | ForEach-Object {$_.Line}\r\n\r\nIs not the true Tao, until\r\nand the presence of absence:\r\n<\/code><\/pre>\n<p>To understand how this works, you first have to know that <strong>Select-String<\/strong> returns an <strong>array<\/strong> of <strong>MatchInfo<\/strong> objects when there is more than one match. To demonstrate this:<\/p>\n<pre><code>$mymatches = Select-String the haiku.txt -CaseSensitive  #Put all matches in the variable 'mymatches'\r\n$mymatches -is [Array]          #query if 'match' is an array\r\n\r\nTrue\r\n<\/code><\/pre>\n<p>So, mymatches is an array. We can see how many elements it has using the array&#8217;s Count property<\/p>\n<pre><code>$mymatches.Count\r\n\r\n2\r\n<\/code><\/pre>\n<p>The type of elements in PowerShell arrays don&#8217;t necessarily have to be the same. In this case, however, they are.<\/p>\n<pre><code>$mymatches[0].gettype() \r\n$mymatches[1].gettype()\r\n<\/code><\/pre>\n<p>both of these give the output<\/p>\n<pre><code>IsPublic IsSerial Name                                     BaseType\r\n-------- -------- ----                                     --------\r\nTrue     False    MatchInfo                                System.Object\r\n<\/code><\/pre>\n<p>If all you wanted was the name of the first object type, you&#8217;d do<\/p>\n<pre><code>$mymatches[0].gettype().name\r\n\r\nMatchInfo\r\n<\/code><\/pre>\n<p>Alternatively, we could have asked for each element&#8217;s type using the <strong>For-Each-Object<\/strong> Cmdlet to loop over every object in the array.<\/p>\n<pre><code>$mymatches | Foreach-Object {$_.gettype().Name}\r\n<\/code><\/pre>\n<p>Where <strong>$_<\/strong> is a special variable that effectively means &#8216;current object&#8217; or &#8216;The object currently being considered by Foreach-Object&#8217; if you want to be more verbose.<\/p>\n<p>So, we know that we have an array of 2 MatchInfo objects in our variable mymatches. What does this mean? What properties do MatchInfo objects have? We can find out by piping one of them to the <strong>Get-Member<\/strong> Cmdlet.<\/p>\n<pre><code>$mymatches[0] | Get-Member\r\n\r\n   TypeName: Microsoft.PowerShell.Commands.MatchInfo\r\n\r\nName         MemberType Definition\r\n----         ---------- ----------  \r\nEquals       Method     bool Equals(System.Object obj)\r\nGetHashCode  Method     int GetHashCode()\r\nGetType      Method     type GetType()\r\nRelativePath Method     string RelativePath(string directory)\r\nToString     Method     string ToString(), string ToString(string directory)\r\nContext      Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;se\r\nFilename     Property   System.String Filename {get;}\r\nIgnoreCase   Property   System.Boolean IgnoreCase {get;set;}\r\nLine         Property   System.String Line {get;set;}   \r\nLineNumber   Property   System.Int32 LineNumber {get;set;}\r\nMatches      Property   System.Text.RegularExpressions.Match[] Matches {get;set;}\r\nPath         Property   System.String Path {get;set;}\r\nPattern      Property   System.String Pattern {get;set;}    \r\n<\/code><\/pre>\n<p>Now we can see that each MatchInfo object has a Line property and it&#8217;s reasonable to guess that this contains a Line containing a match. Taking a look:<\/p>\n<pre><code>$mymatches[0].Line\r\n\r\nIs not the true Tao, until\r\n<\/code><\/pre>\n<p>Bringing together everything we&#8217;ve seen so far, we pull out the Line property of each element in the array as follows<\/p>\n<pre><code>$mymatches | Foreach-Object {$_.Line}\r\n<\/code><\/pre>\n<p>Alternatively, we can ditch the $mymatches variable and pipe in the output of <strong>Select-String<\/strong> directly<\/p>\n<pre><code>Select-String the haiku.txt -CaseSensitive | ForEach-Object {$_.Line}\r\n\r\nIs not the true Tao, until\r\nand the presence of absence:\r\n<\/code><\/pre>\n<h2>Regular expressions<\/h2>\n<pre><code>select-string 's*is' haiku.txt        # * Zero or more of preceding token\r\nselect-string 's+is' haiku.txt        # + On or more of preceding token\r\nselect-string '.nd'  haiku.txt        # . Any token followed by 'nd'\r\nselect-string 'es'   haiku.txt        # matches 'es'\r\nselect-string 'es[ht]' haiku.txt      # Exactly one of the characters listed\r\nselect-string 'es[^ht]' haiku.txt     # Matches everything except h and t\r\nselect-string 'ex[\r\nselect-string '\\bis\\b' haiku.txt      # \\b word boundaries\r\n<\/code><\/pre>\n<h2>Input and output redirection<\/h2>\n<p><code>&gt;<\/code> redirects output (AKA standard output). This works in both Bash and Powershell scripts. For example, in Bash we might do<\/p>\n<pre><code>#BASH\r\ngrep -r not * &gt; found_nots.txt\r\n<\/code><\/pre>\n<p>Drawing on what we&#8217;ve learned so far, you might write the PowerShell version of this command as<\/p>\n<pre><code>#PS\r\nget-childitem *.txt -recurse | select-string not &gt; found_nots.txt\r\n<\/code><\/pre>\n<p>However, if you do this, you will find that the script will run forever with the hard-disk chugging like crazy. If you&#8217;ve run the above command, <strong>CTRL and C<\/strong> will stop it. This is because Powershell is including the output file, found_nots.txt, in its input which leads to an infinite loop. To prevent this, we must explicitly exclude the output file from the <strong>get-childitem<\/strong> search<\/p>\n<pre><code>get-childitem *.txt -Exclude 'found_nots.txt' -recurse | select-string not &gt; found_nots.txt\r\n\r\ncat found_nots.txt\r\nls *.txt &gt; txt_files.txt\r\ncat txt_files.txt\r\n<\/code><\/pre>\n<p>In Linux, <code>&lt;<\/code> redirects input (AKA standard input). This does not work in PowerShell:<\/p>\n<pre><code>cat &lt; haiku.txt\r\nAt line:1 char:5\r\n+ cat &lt; haiku.txt\r\n+     ~\r\nThe '&lt;' operator is reserved for future use.\r\n    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException\r\n    + FullyQualifiedErrorId : RedirectionNotSupported\r\n<\/code><\/pre>\n<p>The above is a forced use of &lt; since one could simply do<\/p>\n<pre><code>cat haiku.txt\r\n<\/code><\/pre>\n<p>Recall that <strong>cat<\/strong> is an alias for <strong>get-content<\/strong>. The use of <strong>get-content<\/strong> is an idiom that gets around the lack an &lt; operator. For example, instead of<\/p>\n<pre><code>foo &lt; input.txt\r\n<\/code><\/pre>\n<p>One does<\/p>\n<pre><code>get-content input.txt | foo\r\n<\/code><\/pre>\n<p>Error messages are output on standard error<\/p>\n<pre><code>ls idontexist.txt &gt; output.txt  \r\ncat output.txt                  #output.txt is empty\r\nls idontexist.txt 2&gt; output.txt               # 2 is standard error\r\nls haiku.txt 1&gt; output.txt                    # 1 is standard output\r\nls haiku.txt,test_file.txt 2&gt;&amp;1 &gt; output.txt  # Combine the two streams.\r\n<\/code><\/pre>\n<h2><span style=\"font-size: 1.5em;\">Searching for files<\/span><\/h2>\n<pre><code># Find all     \r\nUNIX: find .\r\nPS: get-childitem .  -Recurse \r\nPS: get-childitem .  -Recurse | foreach-object {$_.FullName}    #To give identical output as `find`   \r\n<\/code><\/pre>\n<p>To save on typing, you can use the alias <strong>gci<\/strong> instead of <strong>get-childitem<\/strong><\/p>\n<pre><code># Directories only\r\nUNIX: find . -type d        \r\nPS2: gci . -recurse | where { $_.PSIsContainer }\r\nPS3: gci -recurse -Directory \r\n<\/code><\/pre>\n<p>If you have PowerShell 2, you can only use the long winded version. It&#8217;s simpler in PowerShell 3. Similarly for searching for files only.<\/p>\n<pre><code># Files only\r\nUNIX: find . -type f          \r\nPS2: get-childitem -recurse | where { ! $_.PSIsContainer }\r\nPS3: gci -recurse -File\r\n<\/code><\/pre>\n<p>With the Unix find command, you can specify the maximum and minimum search depths. There is no direct equivalent in PowerShell although you could write a function that will do this. Such a function can be found at <a href=\"http:\/\/windows-powershell-scripts.blogspot.co.uk\/2009\/08\/unix-linux-find-equivalent-in.html\">http:\/\/windows-powershell-scripts.blogspot.co.uk\/2009\/08\/unix-linux-find-equivalent-in.html<\/a> although <strong>I have not tested this!<\/strong><\/p>\n<pre><code># Maximum depth of tree\r\nUNIX: find . -maxdepth 2\r\nPS : No direct equivalent\r\n# Minimum depth of tree\r\nUNIX: find . -mindepth 3 \r\nPS : No direct equivalent\r\n<\/code><\/pre>\n<p>You can also filter by name. Confusingly, PowerShell offers two ways of doing this. More details on the differences between these can be found at <a href=\"http:\/\/tfl09.blogspot.co.uk\/2012\/02\/get-childitem-and-theinclude-and-filter.html\">http:\/\/tfl09.blogspot.co.uk\/2012\/02\/get-childitem-and-theinclude-and-filter.html<\/a><\/p>\n<p>One key difference between find and get-childitem is that the latter is case-insenstive whereas find is case sensitive.<\/p>\n<pre><code># Find by name\r\nUNIX: find . -name '*.txt' \r\nPS: gci -recurse -include *.txt\r\nPS: gci -recurse -filter *.txt\r\n\r\n#Find empty files\r\nUNIX: find . -empty    \r\nPS: gci -recurse | where ($_.Length -eq 0) | Select FullName \r\n\r\n#Create empty file\r\nUNIX: touch emptyfile.txt\r\nPS: new-item emptyfile.txt -type file\r\n<\/code><\/pre>\n<h2>Command Substituion<\/h2>\n<p>In bash, you can execute a command using backticks and the result is substituted in place. i.e.<\/p>\n<pre><code>#bash\r\nfoo `bar`\r\n<\/code><\/pre>\n<p>The backticks are used as escape characters in PowerShell so you do the following instead<\/p>\n<pre><code>#PS\r\nfoo $(bar)\r\n<\/code><\/pre>\n<p>In both cases, the command <strong>bar<\/strong> is executed and result is substituted into the call to <strong>foo<\/strong>.<\/p>\n<h2>Power of the pipe<\/h2>\n<p><code>|<\/code> is a pipe. Use the pipe to connect the output of one command to the input of another:<\/p>\n<p>Count text files<\/p>\n<pre><code>ls -filter *.txt | measure\r\n<\/code><\/pre>\n<p><code>ls<\/code> outputs a list of files, <code>measure<\/code> inputs a list of files.<\/p>\n<pre><code>echo \"Number of .txt files:\"; ls -filter *.txt | measure | select -expand count\r\n<\/code><\/pre>\n<p><code>;<\/code> equivalent to running two commands on separate lines.<\/p>\n<p>Question: what does this do?<\/p>\n<pre><code>ls -name | select-string s | measure\r\n<\/code><\/pre>\n<p>Answer: counts the number of files with <code>s<\/code> in their name.<\/p>\n<pre><code>history | select-string 'echo'\r\n<\/code><\/pre>\n<p>Power of well-defined modular components with well-defined interfaces,<\/p>\n<ul>\n<li>Bolt together to create powerful computational and data processing workflows.<\/li>\n<li>Good design principle applicable to programming &#8211; Python modules, C libraries, Java classes &#8211; modularity and reuse.<\/li>\n<li>&#8220;little pieces loosely joined&#8221; &#8211; <code>history<\/code> + <code>select-string<\/code> = function to search for a command.<\/li>\n<\/ul>\n<h2>Variables<\/h2>\n<pre><code>get-variable                            # See all variables\r\n$MYFILE=\"data.txt\"                      # Need quotes around strings\r\necho $MYFILE\r\necho \"My file name is $MYFILE\"\r\n$num = 1                                #Numbers don't need quotes\r\n$num = $num+1                           #Simple Arithmetic\r\n$TEXT_FILES=get-childitem               Save output of get-childitem\r\necho $TEXT_FILES\r\n<\/code><\/pre>\n<p>Variables only persist for the duration of the current PowerShell Session<\/p>\n<h2>Environment variables<\/h2>\n<p>Windows environment variables don&#8217;t show up when you execute <code>get-variable<\/code>; to list them all you do<\/p>\n<pre><code>#PS\r\nget-childitem env:                  #Show all Windows Environment variables \r\necho $env:PATH                      #Show the contents of PATH\r\n$env:Path = $env:Path + \";C:\\YourApp\\bin\\\"  #temporarily add a folder to PATH\r\n<\/code><\/pre>\n<p>This modification to PATH will only last as long as the current session. It is possible to permanently modify the system PATH but this should only be done with extreme care and is not covered here.<\/p>\n<h2>PowerShell Profile<\/h2>\n<p>The PowerShell profile is a script that is executed whenever you launch a new session. Every user has their own profile. The location of your PowerShell profile is defined by the variable <strong>$profile<\/strong><\/p>\n<pre><code>$profile\r\n<\/code><\/pre>\n<p>Open it with<\/p>\n<pre><code>notepad $profile\r\n<\/code><\/pre>\n<p>Add something to it such as<\/p>\n<pre><code>echo \"Welcome to PowerShell.  This is a message from your profile\"\r\n<\/code><\/pre>\n<p>Restart PowerShell and you should see the message. You can use this profile to customise your PowerShell sessions. For example, if you have installed NotePad++, you might find adding the following function to your PowerShell Profile to be useful.<\/p>\n<pre><code># Launch notepad++ using the npp command\r\nfunction npp($file)\r\n{\r\nif ($file -eq $null)\r\n    {\r\n        &amp; \"C:\\Program Files (x86)\\Notepad++\\notepad++.exe\";\r\n    }\r\n    else\r\n    {\r\n        &amp; \"C:\\Program Files (x86)\\Notepad++\\notepad++.exe\" $file;\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>With this function in your profile, you can open Notepad++ with the command <strong>npp<\/strong> or <strong>npp(filename.txt)<\/strong><\/p>\n<h2>Conditionals<\/h2>\n<pre><code>$num = 1\r\nif($num -eq 1)\r\n{\r\n  write-host 'num equals 1'\r\n}\r\n\r\n$word=\"hello\"\r\nif($word -eq \"hello\")\r\n{\r\n  write-host 'The same'\r\n}\r\n<\/code><\/pre>\n<p>By default, string tests are case insensitive<\/p>\n<pre><code>$word=\"hello\"\r\nif($word -eq \"HELLO\")\r\n{\r\n  write-host 'The same'\r\n}\r\n<\/code><\/pre>\n<p>To force them to be case sensitive, add a <code>c<\/code> to the operator:<\/p>\n<pre><code>$word=\"hello\"\r\nif($word -ceq \"HELLO\")\r\n{\r\n  write-host 'The Same. This won't be printed'\r\n}\r\n<\/code><\/pre>\n<p>You can similarly be explicitly case insensitive by adding an <code>i<\/code>. Although this is the the same behaviour as the undecorated operators and so might seem unnecessary, it shows your intent to the reader.<\/p>\n<pre><code>    $word=\"hello\"\r\n    if($word -ieq \"HELLO\")\r\n    {\r\n      write-host 'The same'\r\n    }\r\n<\/code><\/pre>\n<h5>Comparison Operators<\/h5>\n<pre><code>-eq Equal to\r\n-lt Less than\r\n-gt Greater than\r\n-ge Greater than or equal to\r\n-le Less than or equal to\r\n-ne Not equal to\r\n<\/code><\/pre>\n<h5>Logical operators<\/h5>\n<pre><code>-not    Not\r\n!       Not\r\n-or     Or\r\n-and    And\r\n<\/code><\/pre>\n<h2>Loops<\/h2>\n<p>PowerShell has several looping constructs. Here, I only consider two.<\/p>\n<h3>for<\/h3>\n<p>Allows you to run a block of code a set number of times.<\/p>\n<pre><code>for ($i=1; $i -le 5; $i=$i+1)\r\n{\r\n   Write-Host $i\r\n}\r\n<\/code><\/pre>\n<h3>foreach<\/h3>\n<p>Do something with every element of a collection<\/p>\n<pre><code>foreach($item in $(ls *.txt)) {echo $item.Name}\r\n<\/code><\/pre>\n<h3>foreach vs foreach-object<\/h3>\n<p>TODO<\/p>\n<h2>Shell scripts<\/h2>\n<ul>\n<li>Save retyping.<\/li>\n<li>PowerShell scripts have the extension .ps1<\/li>\n<li>PowerShell scripts must be created with plain text editors such as Notepad or Notepad++. NEVER use Microsoft Word!<\/li>\n<\/ul>\n<p>Here is a very simple script<\/p>\n<pre><code>notepad protein_filter.ps1                  #Open the file\r\n\r\n#A simple protein filter\r\n$DATE = get-date\r\necho \"Processing date: $DATE\"\r\n\r\nforeach($item in get-childitem *.pdb)\r\n{\r\n   echo $item.Name\r\n}\r\n\r\necho \"Processing complete\"\r\n<\/code><\/pre>\n<p>To run this just type the filename:<\/p>\n<pre><code>protein_filter.ps1\r\n<\/code><\/pre>\n<p>If you get an error message, it may be because your execution policy is set not to run scripts locally. Change this with the command<\/p>\n<pre><code>Set-ExecutionPolicy RemoteSigned            #Allow local scripts to run.  Needs to be run with admin privileges\r\nprotein_filter.ps1                          #Run the script\r\n<\/code><\/pre>\n<h2><\/h2>\n<h2>Download files via command-line<\/h2>\n<p><a href=\"http:\/\/data.gov.uk\/dataset\/primary-care-trust-prescribing-data-april-to-june-2011\">Primary Care Trust Prescribing Data &#8211; April 2011 onwards<\/a><\/p>\n<pre><code>$file=\"prim-care-trus-pres-data-apr-jun-2011-dat.csv\"\r\n$path=\"$pwd\\$file\"   #Path needs to be fully qualified. This puts it in the current folder\r\n$url = \"http:\/\/www.ic.nhs.uk\/catalogue\/PUB02342\/$file\"\r\n$client = new-object System.Net.WebClient\r\n$client.DownloadFile( $url, $path )\r\n<\/code><\/pre>\n<h2>Permissions<\/h2>\n<p>Windows file permissions are rather more complicated than those of Linux but most users won&#8217;t need to worry about them in day to day use.<\/p>\n<h2>The call operator<\/h2>\n<p>It is sometimes convenient to construct strings that contain the full path to a PowerShell script we want to execute. For example:<\/p>\n<pre><code>$fullpath = \"$pwd\\myscript.ps1\"\r\n<\/code><\/pre>\n<p>To actually run the script pointed to by this variable, you need to use the call operator <strong>&amp;<\/strong><\/p>\n<pre><code>&amp; $fullpath                 #Runs myscript.ps1\r\n<\/code><\/pre>\n<p>You also need to do this if you try to call any script where the path contains spaces<\/p>\n<pre><code>\"C:\\Program Files\\myscript.ps1\"             #Will just display the string literal\r\n&amp; \"C:\\Program Files\\myscript.ps1\"           #Runs the script\r\n<\/code><\/pre>\n<h2>Background Jobs<\/h2>\n<p>Consider the script counter.ps1<\/p>\n<pre><code>param($step=1)\r\n#counter.ps1: A simple, long running job to demonstrate background jobs\r\n\r\n$i=1\r\n\r\nwhile ( $i -lt 200000 )\r\n{\r\n    echo $i\r\n    $i=$i+$step\r\n}\r\n<\/code><\/pre>\n<p>This counts up to 200000 in user-defined steps.<\/p>\n<pre><code>.\/counter.ps1   &gt; 1step.txt                 #Counts in steps of 1\r\n.\/counter.ps1 -step 2 &gt; 2step.txt           #Counts in steps of 2\r\n<\/code><\/pre>\n<p>The script takes quite a while to complete and you cannot do anything else in your PowerShell session while it is working. Using the <strong>start-job<\/strong> Cmdlet, we can run counter.ps1 in the background<\/p>\n<pre><code>start-job -scriptblock { C:\\Users\\walkingrandomly\\Dropbox\\SSI_Windows\\dir_full_of_files\\some_directory\\counter.ps1 &gt;  C:\\Users\\walkingrandomly\\Dropbox\\SSI_Windows\\dir_full_of_files\\some_directory\\outcount1.txt }\r\n<\/code><\/pre>\n<p>Note that you have to use the <strong>full path<\/strong> to both the counter.ps1 script and the output file. You can see the status of the job with <strong>get-job<\/strong><\/p>\n<pre><code>get-job\r\n\r\nId              Name            State      HasMoreData     Location             Command\r\n--              ----            -----      -----------     --------             -------\r\n1              Job1             Running    True            localhost             C:\\Users\\walkingrando...\r\n<\/code><\/pre>\n<p>Eventually, your job will complete<\/p>\n<pre><code>get-job\r\n\r\nId              Name            State      HasMoreData     Location             Command\r\n--              ----            -----      -----------     --------             -------\r\n1               Job1            Completed  False           localhost             C:\\Users\\walkingrando...\r\n\r\nls outcount*                    #Ensure that output file has been created\r\nremove-job 1                    #remove remnants of job 1 from the queue\r\nget-job                         #Check that queue is empty\r\n<\/code><\/pre>\n<p>You can run as many simultaneous jobs as you like but it is best not to run too many or your computer will grind to a halt.<\/p>\n<p>Here&#8217;s an example that runs 5 counter.ps1 jobs concurrently<\/p>\n<pre><code>#parallel_counters.ps1\r\n#Runs 5 instances of counter.ps1 in parallel\r\n\r\n$scriptname     = \"counter.ps1\"\r\n$outputfileBase = \"outfile\"\r\n$outputfileExt  = \".txt\"\r\n\r\n$scriptPath = \"$pwd\\$scriptname\"\r\n\r\nfor ($i=1; $i -le 5; $i++)\r\n{\r\n  $outputfilePath = \"$pwd\\$outputfileBase\" + $i + $outputfileExt\r\n  $command = \"$scriptPath -step $i `&gt; $outputfilePath\"\r\n  $myScriptBlock = [scriptblock]::Create($command)\r\n  start-job -scriptblock $myScriptBlock\r\n}\r\n<\/code><\/pre>\n<p>Run this as a demonstration<\/p>\n<pre><code>parallel_counters.ps1\r\nget-job                             #Keep running until all have completed\r\nls outfile*\r\nmore outfile5.txt\r\nmore outfile2.txt\r\n$myjob=get-job 2                    #Get info on job Id 2 and store in variable $myjob\r\n$myjob.Command                      #Look at the command that comprised job 2\r\nremove-job *                        #Remove all job remnants from the queue\r\nget-job                             #Should be empty\r\n<\/code><\/pre>\n<p>TODO: Dealing with output, <strong>recieve-job<\/strong><\/p>\n<h2>Secure Shell<\/h2>\n<p>There is no equivalent to the Linux commands <strong>ssh<\/strong> and <strong>sftp<\/strong> in PowerShell. The following free programs are recommended<\/p>\n<ul>\n<li><a href=\"http:\/\/www.chiark.greenend.org.uk\/~sgtatham\/putty\/\">http:\/\/www.chiark.greenend.org.uk\/~sgtatham\/putty\/<\/a> &#8211; PuTTY is a free implementation of Telnet and SSH for Windows and Unix platforms.<\/li>\n<li><a href=\"http:\/\/winscp.net\/eng\/index.php\">http:\/\/winscp.net\/eng\/index.php<\/a> &#8211; Free SFTP, SCP and FTP client for Windows.<\/li>\n<li><a href=\"http:\/\/mobaxterm.mobatek.net\/\">http:\/\/mobaxterm.mobatek.net\/<\/a> &#8211; A more advanced terminal than PuTTY with a free &#8216;personal edition&#8217; and a paid-for &#8216;professional edition&#8217;<\/li>\n<\/ul>\n<h2>Packaging<\/h2>\n<p>There are no direct PowerShell equivalents to zip, unzip, tar etc. There are <code>write-zip<\/code>, <code>write-tar<\/code> and <code>write-gzip<\/code> cmdlets in the third party, free <a href=\"http:\/\/pscx.codeplex.com\/\">PowerShell Community Extensions<\/a> but I have not investigated them yet.<\/p>\n<h2>Transcripts<\/h2>\n<p><code>Start-transcript<\/code> Initializes a transcript file which records all subsequent input\/Output. Use the following syntax:<\/p>\n<pre><code>Start-Transcript [[-path] FilePath] [-force] [-noClobber] [-append]\r\n<\/code><\/pre>\n<p><code>Stop-transcript<\/code> Stops recording and finalizes the transcript.<\/p>\n<pre><code>start-transcript -path .\/diary.txt\r\nls\r\necho \"Hello dear diary\"\r\nstop-transcript\r\ncat diary.txt\r\n<\/code><\/pre>\n<ul>\n<li>Record commands typed, commands with lots of outputs, trial-and-error when building software.<\/li>\n<li>Send exact copy of command and error message to support.<\/li>\n<li>Turn into blog or tutorial.<\/li>\n<\/ul>\n<h2>Shell power<\/h2>\n<p>(Bentley, Knuth, McIlroy 1986) <a href=\"http:\/\/dl.acm.org\/citation.cfm?id=5948.315654\">Programming pearls: a literate program<\/a> Communications of the ACM, 29(6), pp471-483, June 1986. DOI: [10.1145\/5948.315654].<\/p>\n<p>Dr. Drang, <a href=\"http:\/\/www.leancrew.com\/all-this\/2011\/12\/more-shell-less-egg\/\">More shell, less egg<\/a>, December 4th, 2011.<\/p>\n<p>Common words problem: read a text file, identify the N most frequently-occurring words, print out a sorted list of the words with their frequencies.<\/p>\n<p>10 plus pages of Pascal &#8230; or &#8230; 1 line of shell<\/p>\n<pre><code>#BASH version\r\n$ nano words.sh\r\ntr -cs A-Za-z '\\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed ${1}q\r\n$ chmod +x words.sh\r\n$ nano words.sh &lt; README.md\r\n$ nano words.sh &lt; README.md 10\r\n<\/code><\/pre>\n<p>The PowerShell version is more complicated but still very short compared to the 10 pages of Pascal<\/p>\n<pre><code>#count_words.ps1\r\nParam([string]$filename,[int]$num)\r\n$text=get-content $filename\r\n$split = foreach($line in $text) {$line.tolower() -split \"\\W\"}\r\n$split | where-object{-not [string]::IsNullorEmpty($_)} |  group -noelement | sort Count -Descending | select -first $num\r\n\r\ncount_words.sh README.md 10\r\n<\/code><\/pre>\n<p>&#8220;A wise engineering solution would produce, or better, exploit-reusable parts.&#8221; &#8211; Doug McIlroy<\/p>\n<h1><\/h1>\n<h2>Links<\/h2>\n<ul>\n<li><a href=\"http:\/\/software-carpentry.org\/\">Software Carpentry<\/a>&#8216;s online <a href=\"http:\/\/software-carpentry.org\/4_0\/shell\/\">Bash shell<\/a> lectures.<\/li>\n<li>G. Wilson, D. A. Aruliah, C. T. Brown, N. P. Chue Hong, M. Davis, R. T. Guy, S. H. D. Haddock, K. Huff, I. M. Mitchell, M. Plumbley, B. Waugh, E. P. White, P. Wilson (2012) &#8220;<a href=\"http:\/\/arxiv.org\/abs\/1210.0530\">Best Practices for Scientific Computing<\/a>&#8220;, arXiv:1210.0530 [cs.MS].<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Update: September 2014 &#8211; The notes in this blog post have been uploaded to github: https:\/\/github.com\/mikecroucher\/Windows_Scientific_Computing. The blog post will be kept as-is for posterity reasons. For the most up to date version of the notes, see the github version. Some time in 2013, I helped out at a Software Carpentry event at The University [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[72,67,69],"tags":[],"class_list":["post-5523","post","type-post","status-publish","format-standard","hentry","category-powershell","category-scientific-software","category-software-deployment"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p3swhs-1r5","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts\/5523","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5523"}],"version-history":[{"count":10,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts\/5523\/revisions"}],"predecessor-version":[{"id":5564,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=\/wp\/v2\/posts\/5523\/revisions\/5564"}],"wp:attachment":[{"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5523"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5523"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/walkingrandomly.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5523"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}