Powershell `echo -e "1\n2\n3" | tail -n1` equivalent - powershell

There are countless examples about how to get tail for file content, but there is no for pipes.
Is there any way to get last line from pipe ?
I have long chain of piped commands and I want last 1 line only.

If you want the last object, that is send through the pipe, Select-Object can do that for you:
$input | Select-Object -Last 1
If you are only interested in one particular property of this object, you can expand this property in the same statement:
[pscustomobject]#{propname = 'foo'},[pscustomobject]#{propname = 'bar'} |
Select-Object -Last 1 -ExpandProperty propname

Or -1 is the last element of an array.
(echo 1 2 3)[-1]
3

Related

Word frequency elegantly in Powershell

Donald Knuth once got the task to write a literate program computing the word frequency of a file.
Read a file of text, determine the n most frequently used words, and print out a sorted list of those words along with their frequencies.
Doug McIlroy famously rewrote the 10 pages of Pascal in a few lines of sh:
tr -cs A-Za-z '\n' |
tr A-Z a-z |
sort |
uniq -c |
sort -rn |
sed ${1}q
As a little exercise, I converted this to Powershell:
(-split ((Get-Content -Raw test.txt).ToLower() -replace '[^a-zA-Z]',' ')) |
Group-Object |
Sort-Object -Property count -Descending |
Select-Object -First $Args[0] |
Format-Table count, name
I like that Powershell combines sort | uniq -c into a single Group-Object.
The first line looks ugly, so I wonder if it can be written more elegantly? Maybe there is a way to load the file with a regex delimiter somehow?
One obvious way to shorten the code would be to uses the aliases, but that does not help readability.
I would do it this way.
PS C:\users\me> Get-Content words.txt
One one
two
two
three,three.
two;two
PS C:\users\me> (Get-Content words.txt) -Split '\W' | Group-Object
Count Name Group
----- ---- -----
2 One {One, one}
4 two {two, two, two, two}
2 three {three, three}
1 {}
EDIT: Some code from Bruce Payette's Windows Powershell in Action
# top 10 most frequent words, hash table
$s = gc songlist.txt
$s = [string]::join(" ", $s)
$words = $s.Split(" `t", [stringsplitoptions]::RemoveEmptyEntries)
$uniq = $words | sort -Unique
$words | % {$h=#{}} {$h[$_] += 1}
$frequency = $h.keys | sort {$h[$_]}
-1..-10 | %{ $frequency[$_]+" "+$h[$frequency[$_]]}
# or
$grouped = $words | group | sort count
$grouped[-1..-10]
Thanks js2010 and LotPings for important hints. To document what is probably the best solution:
$Input -split '\W+' |
Group-Object -NoElement |
Sort-Object count -Descending |
Select-Object -First $Args[0]
Things I learned:
$Input contains stdin. This is closer to McIlroys code than Get-Content some file.
split can actually take regex delimiters
the -NoElement parameter let me get rid of the Format-Table line.
Windows 10 64-bit. PowerShell 5
How to find what whole word (the not -the- or weather) regardless of case is most frequently used in a text file and how many times it is used using Powershell:
Replace 1.txt with your file.
$z = gc 1.txt -raw
-split $z | group -n | sort c* | select -l 1
Results:
Count Name
----- ----
30 THE

Delete all lines which contain a duplicate word

I want to delete all the lines that contain one string and keep just the last line.
Eg:
a 1
a 2
a 3
b 1
b 2
I want to delete: a 1 a 2 b 1 and keep only the last lines: a 3 b 2.
I have tried something in powershell but witout success:
gc 1.txt | sort | get-unique
Assuming that you want to:
consider lines that share the same word at the start (a or b in your example) as group,
and return the last line from each such group,
use the Group-Object cmdlet:
Get-Content 1.txt | Group-Object { (-split $_)[0] } | ForEach-Object { $_.Group[-1] }
{ (-split $_)[0] } uses a property expression, via a script block ({ ... }, rather than a property name as the grouping criterion.
-split $_ splits each input line ($_) into an array of substrings by whitespace.
(...)[0] extracts the 1st token, i.e. the first whitespace-separated token on the line (a or b, in your sample data)
As for what you tried (showing your command with aliases expanded):
Get-Content 1.txt | Sort-Object | Get-Unique
Your Sort-Object and Get-Unique calls both operate on the full lines, which is not your intent: since all lines are unique when considered in full, they are all output.
Note that Sort-Object has a -Unique switch, so the following would come closer to what you want, but it wouldn't allow you to control which of the lines that share the same first word to return:
# !! INCORRECT, because you don't control which of the duplicates
# !! is returned, given that sorting is based on only the *first* word
# !! on each line.
PS> Get-Content 1.txt | Sort-Object { (-split $_)[0] } -Unique
a 1
b 1

Powershell counting same values from csv

Using PowerShell, I can import the CSV file and count how many objects are equal to "a". For example,
#(Import-csv location | where-Object{$_.id -eq "a"}).Count
Is there a way to go through every column and row looking for the same String "a" and adding onto count? Or do I have to do the same command over and over for every column, just with a different keyword?
So I made a dummy file that contains 5 columns of people names. Now to show you how the process will work I will show you how often the text "Ann" appears in any field.
$file = "C:\temp\MOCK_DATA (3).csv"
gc $file | %{$_ -split ","} | Group-Object | Where-Object{$_.Name -like "Ann*"}
Don't focus on the code but the output below.
Count Name Group
----- ---- -----
5 Ann {Ann, Ann, Ann, Ann...}
9 Anne {Anne, Anne, Anne, Anne...}
12 Annie {Annie, Annie, Annie, Annie...}
19 Anna {Anna, Anna, Anna, Anna...}
"Ann" appears 5 times on it's own. However it is a part of other names as well. Lets use a simple regex to find all the values that are only "Ann".
(select-string -Path 'C:\temp\MOCK_DATA (3).csv' -Pattern "\bAnn\b" -AllMatches | Select-Object -ExpandProperty Matches).Count
That will return 5 since \b is for a word boundary. In essence it is only looking at what is between commas or beginning or end of each line. This omits results like "Anna" and "Annie" that you might have. Select-Object -ExpandProperty Matches is important to have if you have more than one match on a single line.
Small Caveat
It should not matter but in trying to keep the code simple it is possible that your header could match with the value you are looking for. Not likely which is why I don't account for it. If that is a possibility then we could use Get-Content instead with a Select -Skip 1.
Try cycling through properties like this:
(Import-Csv location | %{$record = $_; $record | Get-Member -MemberType Properties |
?{$record.$($_.Name) -eq 'a';}}).Count

Return first matching line

I have an XML command that returns a list of URLs, example
PS > $xml.rss.channel.item.link
http://example.com/20140704.exe
http://example.com/20140704.tar.xz
http://example.com/20140624.exe
http://example.com/20140624.tar.xz
http://example.com/20140507.tar.xz
From this list, I would like to return the first .tar.xz line. I have this
command
$xml.rss.channel.item.link | ? {$_ -match '.tar.xz'} | select -first 1
But I would prefer a command with only one pipe if possible.
You don't need a pipe at all:
(Select-Xml -Xml $xml -XPath "(//link[contains(.,'.tar.xz')])[1]").Node.InnerText
Note: XPath is case-sensitive. If that is an issue, you can use a trick with translate() function and force it to ignore the case.
A different way using two pipes
$xml.rss.channel.item.link | Select-String .tar.xz | select -first 1
One pipe
($xml.rss.channel.item.link | Select-String .tar.xz)[0]

analog command grep -o in Powershell

What command in Powershell replaces grep -o (which displays only the matched portion of a line instead of a whole line) ?
i try use Select-Object but it always display full line.
For example:
next line
<a id="tm_param1_text1_item_1" class="tm_param1param2_param3 xxx_zzz qqq_rrrr_www_vv_no_empty" >eeee <span id="ttt_xxx_zzz">0</span></a>
use next command:
cat filename | grep -o '>[0-9]' | grep -o '[0-9]'
output: 0
When i use Select-Object i always see full line (
One way is:
$a = '<a id="tm_param1_text1_item_1" class="tm_param1param2_param3 xxx_zzz qqq_rrrr_www_vv_no_empty" >eeee <span id="ttt_xxx_zzz">0</span></a>'
$a -match '>([0-9])<' #returns true and populate the $matches automatic variable
$matches[1] #returns 0
For selecting strings in text, use select-string rather than select-object. It will return a MatchInfo object. You can access the matches by querying the matches property:
$a = '<a id="tm_param1_text1_item_1" class="tm_param1param2_param3 xxx_zzz qqq_rrrr_www_vv_no_empty" >eeee <span id="ttt_xxx_zzz">0</span></a>'
($a | select-string '>[0-9]').matches[0].value # returns >0
InPowerShell v3:
sls .\filename -pattern '^[0-9]' -AllMatches | % matches | % value
Explanation:
sls is an alias for Select-String. It takes a filename/path as well as a pattern as parameters. It produces "matches"
% matches selects all matches regardless of file etc.
% value selects the value of each match
The solutions that have been proposed so far only produce the first match from each line. To fully emulate the behavior of grep -o (which produces every match from each line) something like this is required:
Get-Content filename | Select-String '>([0-9])' -AllMatches |
Select-Object -Expand Matches | % { $_.Groups[1].Value }
Select-String -AllMatches returns all matches from an input string.
Select-Object -Expand Matches "disconnects" matches from the same line, so that all submatches can be selected via $_.Groups[1]. Without this expansion the submatch from the second match of a line would be $_.Groups[3].