Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
203 views
in Technique[技术] by (71.8m points)

Powershell print only even rows from a text file

File content

"Line 1 : Dear Cinderella,

Line 2 :

Line 3 :

Line 4 : I think I found your shoe..."

I need the output to be as follows, so just even lines. Powershell sees the Line 1 as row 0, Line2 as row 1, etc. But I don't want to confuse you any further. Here is an example:

"Line 2 :

Line 4 : I think I found your shoe..."

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There are probably thousands of ways to do this, below is a quick and dirty example but it should work.

<#
For demo purposes my file has content like:
line 0
line 1
line 2
line 3
...
#>

$Content = Get-Content 'C:	empNumberedLines.txt'
    
For($i = 0; $i -lt $Content.Length; $i += 2 )
{
    $Content[$i]
}

Note: I started with Line 0 but you can change the initial value of $i if you want to start at line 2 as your question / example suggests.

There is one case that might not work so well. If the file is very large and/or you otherwise don't have enough free RAM to store the data in $Content.

The For loop has the array pre-established because it's going to run until $i is greater than or equal to $Content.Length. However you could do this without storing the content in a variable, like below:

$i = 0

Get-Content 'C:	empNumberedLines.txt' | 
ForEach-Object{
    If( !($i % 2) ) { $_ }
    ++$i
}

I prefer the first approach, but this is an alternate for aforementioned circumstances. Technically I don't have to do an initial assignment on $i, but I think it's a good practice here, especially considering $i is commonly used. As before, you can initiate $i to 2 (or whatever) as needed. Inside the ForEach-Object loop I'm testing if $i is even. If it is I'll output the current pipeline element.

Note: !($i % 2) can also be expressed as $i % 2 -eq 0 like:

$i = 0

Get-Content 'C:	empNumberedLines.txt' | 
ForEach-Object{
    If( $i % 2 -eq 0 ) { $_ }
    ++$i
}

A final note: Conceptually you may consider the file starting at 1 while array elements start at 0. In other words you may not want to print line zero. If that's the case, resist the urge to rebase or anything like that. In either example initiating $i to 1 will rectify the issue. Because line 2 would be in array index 1.


Explanation of Алексей Семенов's Neat Answer:

param ( $exampleFileName = "d:	mpexample.txt" )
@"
line 0
line 1
line 2
line 3
...
"@ | Out-File $exampleFileName

(Get-Content $exampleFileName | Group-Object {$Global:i % 2;$Global:i++} | Select-Object -First 1).Group

A neat usage of Group-Object. However, it's confusing to the untrained eye. Questions and answers are not a single affair, and are meant to be discoverable and informative to users down the road. I think this is an interesting sample and worthy of explanation, and since @Алексей Семенов seems unwilling to do so, I'll reluctantly attempt it here.

Note: The Param(..) block is completely unnecessary. It's only part of establishing the demo data/file.

Group-Object can group objects based on the output of an expression. In fact a similar example is right in the Group-Object help documentation. When an expression is used its output becomes "Name" property on the returned GroupInfo objects. Running Get-Content $exampleFileName | Group-Object {$Global:i % 2;$Global:i++} by itself would return something like below.

Count Name  Group
----- ----  -----
3 0     {line 0, line 2, Line 4}
3 1     {line 1, line 3, ...}

Since the increment of $i inside the expression is 1 and the divisor is always 2 the remainder returned by the % operator can only ever be 0 or 1, and therefore only 2 respective named groups are returned.

It appears that Group-Object internally sorts on the Name property ensuring the group named "0" will be the first element in the returned array of GroupInfo objects. So selecting the first element and Dot referencing the Group property will return the grouped objects themselves which in this case are the even numbered lines.

Another slightly shorter way to write this would be:

(Get-Content $exampleFileName | Group-Object {$Global:i % 2;$Global:i++})[0].Group 

Because this doesn't have the additional pipe to Select-Object this is a little more efficient and competes better with some of my earlier examples.

Note: The $global: scope modifier is required, because each instance of the script block is a child of the calling scope. Therefore, there's no variable visibility between the instances. Even if you predefined $i in the parent scope it wouldn't increment in the parent scope unless $global: is used. This is fundamental to scoping in PowerShell. See about_Scoping for more information.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...