I have a log file like this :
[2021/04/13 18:21:57.577+02:00][VERBOSE] Finished: 0 file(s), 5.23 GB; Average Speed:17.26 MB/s.
I just want to remove all string between the "," and "/s." I tried many times I can't do it correctly.
Can someone help me to do this on Powershell ?
If you do not only need to get the interesting part from the log line, but also need to be able to do math on the number ('5.23 GB') in your example, you need to do some more splitting:
foreach ($line in (Get-Content -Path 'TheFile.log')) {
$interestingPart = ($line -split ',')[-1].Trim()
$logSize, $logAverage = $interestingPart -split ';'
$size, $unit = $logSize -split '\s+'
# calculate the size from both the number ('5.23') and the unit ('GB')
$size = [double]::Parse($size) * "1$unit"
# now you have the number to do further math on
}
Related
Old thread
My question regards:
function GetStringBetweenTwoStrings($firstString, $secondString, $importPath){
#Get content from file
$file = Get-Content $importPath
#Regex pattern to compare two strings
$pattern = "$firstString(.*?)$secondString"
#Perform the opperation
$result = [regex]::Match($file,$pattern).Groups[1].Value
#Return result
return $result
}
GetStringBetweenTwoStrings -firstString "Lorem" -secondString "is" -importPath "C:\Temp\test.txt"
This is nice for only one -firstString and -secondString, but how to use this function to chronologically write multiple same strings in numbered TXT?
txt - file(with more sections of text):
Lorem
....
is
--> write to 001.txt
Lorem
....
is
--> write to 002.txt
and so forth....
And the structure of the section is preserved and is not in one line.
I hope someone can tell me that. Thanks.
The function you quote has several limitations (I've left feedback on the original answer), most notably only ever reporting one match.
Assuming an improved function named Select-StringBetween (see source code below), you can solve your problem as follows:
$index = #{ value = 0 }
Get-ChildItem C:\Temp\test.txt |
Select-StringBetween -Pattern 'Lorem', 'is' -Inclusive |
Set-Content -LiteralPath { '{0:000}.txt' -f ++$index.Value }
Select-StringBetween source code:
Note: The syntax is in part patterned after Select-String. After defining the function, run Select-StringBetween -? to see its syntax; the parameter names are hopefully self-explanatory.
function Select-StringBetween {
[CmdletBinding(DefaultParameterSetName='String')]
param(
[Parameter(Mandatory, Position=0)]
[ValidateCount(2, 2)]
[string[]] $Patterns,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName='File')]
[Alias('PSPath')]
[string] $LiteralPath,
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName='String')]
[string] $InputObject,
[switch] $Inclusive,
[switch] $SimpleMatch,
[switch] $Trim
)
process {
if ($LiteralPath) {
$InputObject = Get-Content -ErrorAction Stop -Raw -LiteralPath $LiteralPath
}
if ($Inclusive) {
$regex = '(?s)(?:{0}).*?(?:{1})' -f
($Patterns[0], [regex]::Escape($Patterns[0]))[$SimpleMatch.IsPresent],
($Patterns[1], [regex]::Escape($Patterns[1]))[$SimpleMatch.IsPresent]
}
else {
$regex = '(?s)(?<={0}).*?(?={1})' -f
($Patterns[0], [regex]::Escape($Patterns[0]))[$SimpleMatch.IsPresent],
($Patterns[1], [regex]::Escape($Patterns[1]))[$SimpleMatch.IsPresent]
}
if ($Trim) {
[regex]::Matches(
$InputObject,
$regex
).Value.Trim()
}
else {
[regex]::Matches(
$InputObject,
$regex
).Value
}
}
}
Note that there's also a pending feature request on GitHub to add this functionality directly to Select-String - see GitHub issue #15136
How can I force conversion to type System.Version in PowerShell, or more likely, better understand why I cannot arbitrarily assign number strings type System.Version?
We ingest some software updates in folders whose titles include version numbers. In trying to get reports on what the latest versions ingested are, I have been doing the following quick and dirty:
ForEach ($Folder in $(Get-ChildItem -Path $SoftwareDirectory -Directory))
{
$CurrentVersion = $Folder -Replace "[^0-9.]"
If ($CurrentVersion -ne $null)
{
If ([System.Version]$CurrentVersion -gt [System.Version]$MaxVersion)
{
$MaxVersion = $CurrentVersion
$MaxFolder = $Folder
}
}
}
This would be fed directory titles like the following,
foo-tools-1.12.file
bar-static-3.4.0.file
Most of the time, this is acceptable. However, when encountering some oddballs with longer numbers, like the following,
applet-4u331r364.file
In which case, System.Version refuses the resulting string as being too long.
Cannot convert value "4331364" to type "System.Version". Error: "Version string portion was too short or too long."
You need to ensure that your version strings have at least two components in order for a cast to [version] to succeed:
(
#(
'oo-tools-1.12.file'
'bar-static-3.4.0.file'
'applet-4u331r364.file'
) -replace '[^0-9.]'
).TrimEnd('.') -replace '^[^.]+$', '$&.0' | ForEach-Object { [version] $_ }
The above transforms 'applet-4u331r364.file' into '4331364.0', which works when cast to [version].
Note that you can avoid the need for .TrimEnd('.') if you exclude the filename extension to begin with: $Folder.BaseName -replace '[^0-9.]'
-replace '^[^.]+$', '$&.0' matches only strings that contain no . chars., in full, i.e. only those that don't already have at least two components; replacement expression $&.0 appends literal .0 to the matched string ($&).
Output (via Format-Table -AutoSize):
Major Minor Build Revision
----- ----- ----- --------
1 12 -1 -1
3 4 0 -1
4331364 0 -1 -1
We have a text file of students and their notes and we have to count how many "1" notes all the students have got.
My code shows how many lines contain the "1" note, but when it finds a "1", it jumps to the next line.
Could you help me please?
for example:
Huckleberry Finn 2 1 4 1 1
Tom Sawyer 3 2 1 4 1
It should be 5, but it gets 2.
$ones = 0
$file= Get-Content notes.txt
foreach ($i in $file) {
if ($i.Split(' ') -eq 1){
$ones ++
}
}
$ones
If all the 1 tokens are whitespace-separated in your input file, as the sample content suggests, try:
# With your sample file content, $ones will receive the number 5.
$ones = (-split (Get-Content -Raw notes.txt) -eq '1').Count
The above uses the unary form of -split, the string splitting operator and the -Raw switch of the Get-Content cmdlet, which loads a file into memory as a single, multi-line string.
That is, the command above splits the entire file into white-space separated tokens, and counts all tokens that equal '1', across all lines.
If, instead, you meant to count the number of '1' tokens per line, use the following approach:
# With your sample file content, $ones will receive the following *array*:
# 3, 2
$ones = Get-Content notes.txt | ForEach-Object { ((-split $_) -eq '1').Count }
As for what you tried:
if ($i.Split(' ') -eq 1)
While $i.Split(' ') does return all (single-)space-separated tokens contained in a single input line stored in $i, using that expression in a conditional expression of an if statement only results in one invocation of the associated { ... } block and therefore increments $ones only by a value of 1, not the number of 1 tokens in the line at hand.
Solved!
Thank you mklement0!
I don't understand why, but it works so:
$ones = 0
$file= Get-Content notes.txt
foreach ($i in $file) {
$ones=(-split ($i) -eq '1').Count
}
}
$ones
I would like to take the following string and parse it in a way to output the length of each step. Time is listed as hr:min:sec.
Edited Desired Output:
[Step 2] took 2 minutes to complete.
[Test 3] took 3 minutes and 3 seconds to complete.
[Example 4] took 3 minutes to complete.
edited to ask that each line item in brackets of $content is output individually rather than simply listing "Step"
$Content =
[Step1]::14:37:11
[Step2]::14:39:11
[Test3]::14:42:14
[Example4]::14:45:14
In this case, I would create an array of TimeSpan objects from the strings in $content and use these for the output.
IF $content is an array, use it straight away.
IF $content is a single string, you need to split it first with $content = $content -split '\r?\n'
# assuming $content is a single string, so we need to split into array:
$Content = #"
[Step1]::14:37:11
[Step2]::14:39:11
[Step3]::14:42:14
[Step4]::14:45:09
[Step5]::14:57:12
"# -split '\r?\n'
Next loop through this string array and remove empty lines if there are any:
$times = $content | Where-Object { $_ -match '\S' } | ForEach-Object {
$h, $m, $s = ($_.Trim() -replace '.*(\d{2}:\d{2}:\d{2})$', '$1') -split ':'
[TimeSpan]::new([int]$h, [int]$m, [int]$s)
}
for ($i = 1; $i -lt $times.Count; $i++) {
$timeDiff = $times[$i] - $times[$i - 1]
# output the times taken for each step
"Step $($i + 1) took $($timeDiff.ToString()) to complete."
}
Output:
Step 2 took 00:02:00 to complete.
Step 3 took 00:03:03 to complete.
Step 4 took 00:02:55 to complete.
Step 5 took 00:12:03 to complete.
Regex details for -replace:
. Match any single character that is not a line break character
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
( Match the regular expression below and capture its match into backreference number 1
\d Match a single digit 0..9
{2} Exactly 2 times
: Match the character “:” literally
\d Match a single digit 0..9
{2} Exactly 2 times
: Match the character “:” literally
\d Match a single digit 0..9
{2} Exactly 2 times
)
$ Assert position at the end of the string (or before the line break at the end of the string, if any)
As per your new requirements in the comment, here's the somewhat altered code:
$Content = #"
[Step1]::14:37:11
[Step2]::14:39:11
[Test3]::14:42:14
[Example4]::14:45:14
"# -split '\r?\n'
$times = $content | Where-Object { $_ -match '\S' } | ForEach-Object {
# capture the action like '[Example4]' and the timestamp part
$action, $processTime = $_.Trim() -split '::'
# split the timestamp into Hours, Minutes, Seconds
$h, $m, $s = ($processTime -replace '.*(\d{2}:\d{2}:\d{2})$', '$1') -split ':'
# output an object with two properties, the action name and a TimeSpan
[PsCustomObject]#{
'Action' = $action
'Time' = [TimeSpan]::new([int]$h, [int]$m, [int]$s)
}
}
# loop through the returned array, get the time difference between steps
for ($i = 1; $i -lt $times.Count; $i++) {
$timeDiff = $times[$i].Time - $times[$i - 1].Time
# output the times taken for each step, in this new requirement rounded to show minutes only
'{0} took {1} minutes to complete' -f $times[$i].Action, [math]::Round($timeDiff.TotalMinutes, 0)
}
Output:
[Step2] took 2 minutes to complete
[Test3] took 3 minutes to complete
[Example4] took 3 minutes to complete
here's a slightly different way to get there ... [grin]
what it does ...
creates a set of time info lines to work with
when ready to do this for real, remove the entire #region/#endregion block and replace it with a call to Get-Content.
filters out the lines that don't have a time info string
iterates thru the lines starting # 1 and ending # the upper bound of the collection
this skips [step1] since your desired output indicates the timing info is the END of each step.
grabs the start time from the previous array item
grabs the step name and end time from the current array item
builds a string using the -f string format operator
calcs the elapsed time by converting the time info to timespan objects & subtracting the start from the end
sends that out to the screen
i was tempted to put the info into an array of PSCustomObjects, but you seem to want only the string output. [grin]
the code ...
#region >>> fake reading in a text file
# in real life, use Get-Content
$InStuff = #'
$Content =
[Step1]::14:37:11
[Step2]::14:39:11
[Step3]::14:42:14
[Step4]::14:45:09
[Step5]::14:57:12
'# -split [System.Environment]::NewLine
#endregion >>> fake reading in a text file
# get rid of the no-time-info lines
$TimeLines = $InStuff -match '::'
foreach ($Index in 1..$TimeLines.GetUpperBound(0))
{
$StartTime = ($TimeLines[$Index - 1] -split '::')[-1]
$Step, $EndTime = $TimeLines[$Index] -split '::'
'{0} took {1} [hh:mm:ss] to complete.' -f $Step, ([timespan]$EndTime - [timespan]$StartTime)
}
output ...
[Step2] took 00:02:00 [hh:mm:ss] to complete.
[Step3] took 00:03:03 [hh:mm:ss] to complete.
[Step4] took 00:02:55 [hh:mm:ss] to complete.
[Step5] took 00:12:03 [hh:mm:ss] to complete.
I have a text file which is comprised of only one line. I have had much trouble with splitting the file into a specific number of characters, then adding a string in front of each chunk of characters.
With a multi-line file, I can add characters to each line very easily using
Get-Content -Path $path | foreach-object {$string + $_} | out-file $output but it is much more complicated with a file with only one line.
For example, if I had a file with these random characters,
(******************************************) and i wanted to add a string to the start of every 10 chars, then it would look like this, (examplestring**********examplestring**********examplestring**********) and so on. I have researched everywhere but I have just managed to add the chars to the end of each chunk of characters.
Does anyone have a way of doing this? Preferably using streamreader and writer as get-content may not work for very large files. Thanks.
Hmm, there are some dynamic parameters applicable to file-system get-content and set-content commands that are close to what you are asking for. For example, if test.txt contains a number of * characters, you might interleave every four * with two + characters with something like this:
get-content .\test.txt -Delimiter "****" | % { "++$_" } | Set-Content test2.txt -NoNewline
I don't know how close that is to a match for what you want, but it's probably useful to know that some of these provider-specific parameters, like '-Delimiter' aren't obvious. See https://technet.microsoft.com/en-us/library/hh847764.aspx under the heading 'splitting large files'.
Alternatively, here's a quick function that reads length-delimited strings from a file.
Set-StrictMode -Version latest
function read-characters( $path, [int]$charCount ) {
begin {
$buffer = [char[]]::new($charCount)
$path = Join-Path $pwd $path
[System.IO.StreamReader]$stream = [System.IO.File]::OpenText($path)
try {
while (!$stream.EndOfStream) {
$len = $stream.ReadBlock($buffer,0,$charCount);
if ($len) {Write-Output ([string]::new($buffer,0,$len))}
}
} catch {
Write-Error -Exception $error[0]
} finally {
[void]$stream.Close()
}
}
}
read-characters .\test.txt -charCount 10 |
% {"+$_"} |
write-host -NoNewline
It could use some parameter checking, but should get you started...
With a manageable file size, you might want to try something like this:
$directory = "C:\\"
$inputFile = "test.txt"
$reader = new-object System.IO.StreamReader("{0}{1}" -f ($directory, $inputFile))
# prefix string of each line
$startString = "examplestring"
# how many chars to put on each line
$range = 10
$outputLine = ""
$line = $reader.ReadLine()
$i = 0
while ($i -lt $line.length) {
$outputLine += $($startString + $line.Substring($i, [math]::min($range, ($line.length - $i))))
$i += $range
}
$reader.Close()
write-output $outputLine
Basically it's using substring to cut out each chunk, prefixing the chumk with given string, and appending to the result variable.
Sample input:
==========================
Sample output:
examplestring==========examplestring==========examplestring======