I am trying to extract the sentences that appear between a particular pattern of word, from a file. The intention is to extract the sentences that appear between the first pair of 'GO' words from the file. The logic implemented here is to split the file based on the word 'GO', and then print the second element of the array(the sentences starting with SET in this example). However, PowerShell is not recognizing the separator (GO); instead it seems to be recognizing 'new line' as the separator, and is printing the second sentence.
Please note that I need to read the file and then get the extraction done.
Content of the file
Home address "TJ One way"
Office address "C company Two way"
GO
SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
GO
Home address "TJ One way"
Office address "C company Two way"
GO
:on error exit
GO
My code
$path = 'D:\Scripts'
$deltaFile = 'GoSampleFile.txt'
$modifiedDelta = 'GoSampleFile1.txt'
New-Item -path $path -Name $modifiedDelta -ItemType file -Force
#Split for each appearing GO, after escaping the double quotes
(Get-Content $path'\'$deltaFile).replace('"', '`"') | Set-Content $path'\'$modifiedDelta
$separator = 'GO'
$modifiedDeltaString = Get-Content $path'\'$modifiedDelta
#Write-Host $modifiedDeltaString
#Write-Host $separator
$goArray = $modifiedDeltaString -split "GO", 0, "SimpleMatch"
Write-Output $goArray[1]
#Housekeeping of the temporary file
Remove-Item $path'\'$modifiedDelta
Use Get-Content -Raw ... to read the contents as one string instead of an array of strings for each line
Might as well be a new answer as there's another problem and I'll provide more detail.
As DAX has said you need to use -Raw as Get-Content returns an array of strings, one for each line. When you use -split on it each element is treated separately.
Eg when used on the following array
[0] "Testing"
[1] "This is a test"
[2] "'tis still a test"
$array -split "is", 0, "SimpleMatch"
[0] "Testing"
[1] "Th"
[2] " "
[3] " a test"
[4] "'t"
[5] " still a test"
When you use the -Raw switch, Get-Content returns the entire file as a single string with newline characters.
The other thing I'll point out is you're escaping the quotes, but this isn't necessary. The reason you need to escape quotes is so PowerShell doesn't assume you're terminating the string:
$t = "This is a "bad" test"
> At line:1 char:18
+ $t = "This is a "bad" test"
+ ~~~~~~~~~~
Unexpected token 'bad" test"' in expression or statement.
You need to escape the quotes so that "bad" is still part of the string.
However when you are reading from a file the quotes are already part of the string:
Get-Content C:\test.txt
> This is a "bad" test
Because you are not typing the quotes into the console, they do not need to be escaped. To show you with your own code, check the full content of your temp file:
Home address `"TJ One way`"
Office address `"C company Two way`"
I can't think of any reason you would need to be doing this. Perhaps if you wanted to copy and paste into a console for some reason but that's it.
This may appear to work for now but only because the SQL query I assume you are trying to run doesn't contain quotes, and while I'm not sure if they are used in SQL it would throw an error if you tried, and regardless it's an extra step you don't need to be doing so you can basically scrap the whole temp file and read straight from the original.
Related
I am trying to solve a somewhat weird problem: I need to replace strings within a raw content by strings from the same content that meet a certain matching criteria. The input data look like this:
apple-beta
apple-alpha_orange-beta
apple-alpha_orange-alpha_cherry-beta
apple-alpha_orange-alpha_kiwi-beta
apple-alpha_orange-alpha_mango-beta
abcd-alpha_efgh-beta
abcd-alpha_efgh-alpha_ijkl-beta
abcd-alpha_efgh-alpha_mnop-beta
The replacment should work as follows: look for all "-beta" strings in the content and delete all according "-alpha" strings (eg because there is "orange-beta" already => all "orange-alpha" should be deleted, because there is "apple-beta" already => all "apple-alpha" should be deleted etc.). The result would look like this:
apple-beta
_orange-beta
__cherry-beta
__kiwi-beta
__mango-beta
abcd-alpha_efgh-beta
abcd-alpha__ijkl-beta
abcd-alpha__mnop-beta
I have tried to achieve this with a number of awkward single replacements and temporary file storages as well as with a while-construction that doesn't work at all:
$whileinput = get-content -raw C:\content-input.txt
while ($whileinput -match "\w+-beta") {
$fullval = $whileinput -match "\w+-beta" -replace "-beta","-alpha"
$whileinput = $whileinput -replace '$fullval',''
}
Any help is very appreciated!
Daniel
I would find all your beta items. Then replace the corresponding alpha items.
$data = Get-Content C:\content-input.txt
$betas = ([regex]::Matches($data,'[^_]*?(?=-beta)').Value -ne '' | Foreach-Object {
[regex]::Escape($_)} ) -join '|'
$data -replace "($betas)-alpha"
Explanation:
[regex]::Matches().Value returns only the matched texts.
[^_]*? lazily matches consecutive characters that are not _. (?=-beta) is a positive lookahead for the text -beta but doesn't include the text in the match.
-ne '' is to filter out blank output.
[regex]::Escape() is not necessarily needed in this case. But it is good practice when your text may have special regex characters that you want to match literally.
$betas contains | delimited items because | is the regex OR. Using () to surround the $betas string allows one of those words to be fully matched before matching -alpha in the replacement.
Get-Content gets the entire contents of a file into a variable, so if anything in your file matches that pattern, it'll loop infinitely (because the contents of the file always match your pattern).
PowerShell is heavily based around the concept of the "pipeline" which you can use in conjunction with the Foreach-Object cmdlet to iterate over each line in a file.
I'm not quite clear on what you want the regexes to do, but I don't think the ones you have will do what you want. Try this.
Get-Content -raw C:\content-input.txt | Foreach-Object {
if($_ -match 'beta$') {
$out+=$_ -replace '\w+-alpha',''
}
}
$out | Out-File .\path-to-output.txt
$_ is the default "pipeline variable" aka the current item in the iteration - in this case the current line. Now at least your loop is working!
I'm parsing a log file that is space delimited for the first 7 elements and then a log message or sentence follows. I know just enough to get around in PS, and I'm learning more each day, so I'm not sure this is the best way to do this and apologies if I'm not leveraging a more efficient means that would be second nature to you. I'm using -split(' ')[n] to extract each field of the log file line by line. I'm able to extract the first parts fine as they are space-delimited, but I'm not sure how to get the rest of the elements up to the end of the line.
$logFile=Get-Content $logFilePath
$dateStamp=$logfile -split(' ')[0]
$timeStamp=$logfile -split(' ')[1]
$requestID=$logfile -split(' ')[3]
$binaryID=$logfile -split(' ')[4]
$logID=$logfile -split(' ')[5]
$action=$logfile -split(' ')[6]
$logMessage=$logfile -split(' ')[?]
This is not a CSV that I can import. I'm more familiar with string manipulation in bash so I am able to successfully replace spaces in the first 7 elements, and the end, with "," :
#!/bin/bash
inputFile="/cygdrive/c/Temp/logfile.log"
outputFile="/cygdrive/c/Temp/test_log.csv"
echo "\"DATE\",\"TIME\",\"HYPEN\",\"REQUESTID\",\"BINARY\",\"PROC_NUMBER\",\"MESSAGE\"" > $outputFile
while read -a line
do
arrLength=$(echo ${#line[#]})
echo \"${line[0]}\",\"${line[1]}\",\"${line[2]}\",\"${line[3]}\",\"${line[4]}\",\"${line[5]}\",\"${line[#]:6:$arrLength}\"
done < $inputFile >> $outputFile
Can you help either printing the array elements from position n to the end, or replacing the spaces appropriately in PS so I have a CSV that I can import? Just trying to avoid the two-step process of converting it in bash, then importing it in PS but I'm still researching. I did find this post Parsing Text file and placing contents into an Array Powershell
for importing the file assuming it's space-delimited and that works for the first 7 elements but not sure about everything after that.
Of course I welcome any other PS solutions such as one of those [something]::SOMETHING things I've seen by googling that might do all this much more seamlessly.
You can specify the maximum number of substrings in which the string is split like this:
$splittedRow = $logfile.split(' ',8)
$dateStamp=$splittedRow[0]
$timeStamp=$splittedRow[1]
$requestID=$splittedRow[3]
$binaryID=$splittedRow[4]
$logID=$splittedRow[5]
$action=$spltttedRow[6]
$logMessage=$splittedRow[7]
As an addition to Viktor Be's answer:
$data = "111 22222 333 4444444 5 6 77 888888 9999999 0" #this is the content of file below for testing purposes
#$data = get-content -path C:\temp\mytest.txt
foreach ($line in $data){
$splitted = $line.split(' ',8)
$line_output= ""
for ($i = 0;$i -lt 7;$i++){
$line_output += "$($splitted[$i]);"
}
$line_output += $splitted[7]
$line_output | out-file "C:\temp\MyCsvThatPowershellCanRead.csv" -append
}
You should be able to iterate over each line in the logfile and get the information you need the way you are doing. However, it's easy to grab the message field, which could include n number of spaces in the log message with a regular expression.
The following regex should work for you. Assuming $line is the current line you are on:
$line -match '(?<=(\S+\s+){6}).*'
$logMessage = $matches[0]
The way this expression works is that it looks for .* (which means any character 0 or more times) that comes after 6 occurences of non-whitespace characters followed by whitespace characters. The .* in this expression should match on your log message.
I've been banging my ahead against the wall trying to do what should be a fairly simple substring search in Powershell.
I have a text file with the following content:
2015-08-30 13:12:59 10944512 DATACLUS1\RandomDBName_FULL_20150823_044919.bak
2015-08-30 13:12:59 11010048 DATACLUS1\RandomDBName_FULL_20150830_050126.bak
I need to pull out the filename(s) ("DATACLUS1\RandomDBName_FULL_20150823_044919.bak") and then compare to see which one was created later by the data stamp (20150823 in this case) and then output to a text file that contains only the full name to be actioned later in the process.
I've gone through regex's, match and substring but can't find a combination which will allow me to reliably pull that data. Once I'm over this hurdle I can move on to the compare.
You could do this without extensive regex if you want (assuming each line in the file is a newline.
# Fetch the lines of your backup file
$lines = (Get-Content .\backup.data);
# Format the items in a proper way
$formattedItems = $lines|Select-Object #{Name="Filename"; Expression={($_ -split " ")[3]}}, #{Name="DataStamp"; Expression={(($_.Trim() -split " ")[-1] -split "_")[-2]}}, #{Name="TimeString"; Expression={(($_.Trim() -split " ")[-1] -split "_")[-1] -replace ".bak" ,""}};; # Sort the items by the new property DateString
$sortedItems=$formattedItems|Sort-Object -Property DataStamp
This will give you a sorted list with properties on which you can select on (e.g
you can use the first [0] of $sortedItems).
If you do not want to sort on DataStamp you could select the DateTime stamp (in the beginning of each line, which would probably be more reliable to sort on).
What is the best way to remove all text in a string after a specific character? In my case "=" and after another character in my case a ,, but keep the text between?
Sample input
=keep this,
Another way to do this is with operator -replace.
$TestString = "test=keep this, but not this."
$NewString = $TestString -replace ".*=" -replace ",.*"
.*= means any number of characters up to and including an equals sign.
,.* means a comma followed by any number of characters.
Since you are basically deleting those two parts of the string, you don't have to specify an empty string with which to replace them. You can use multiple -replaces, but just remember that the order is left-to-right.
$a="some text =keep this,but not this"
$a.split('=')[1].split(',')[0]
returns
keep this
This should do what you want:
C:\PS> if ('=keep this,' -match '=([^,]*)') { $matches[1] }
keep this
This is really old, but I wanted to add my slight variation for anyone else who may stumble across this. Regular expressions are powerful things.
To keep the text which falls between the equal sign and the comma:
-replace "^.*?=(.*?),.*?$",'$1'
This regular expression starts at the beginning of the line, wipes all characters until the first equal sign, captures every character until the next comma, then wipes every character until the end of the line. It then replaces the entire line with the capture group (anything within the parentheses). It will match any line that contains at least one equal sign followed by at least one comma. It is similar to the suggestion by Trix, but unlike that suggestion, this will not match lines which only contain either an equal sign or a comma, it must have both in order.
I referenced #benjamin-hubbard 's answer above to parse the output of dnscmd for A records, and generate a PHP "dictionary"/key-value pairs of IPs and Hostnames. I strung multiple -replace args together to replace text with nothing or tab to format the data for the PHP file.
$DnsDataClean = $DnsData `
-match "^[a-zA-Z0-9].+\sA\s.+" `
-replace "172\.30\.","`$P." `
-replace "\[.*\] " `
-replace "\s[0-9]+\sA\s","`t"
$DnsDataTable = ( $DnsDataClean | `
ForEach-Object {
$HostName = ($_ -split "\t")[0] ;
$IpAddress = ($_ -split "\t")[1] ;
"`t`"$IpAddress`"`t=>`t'$HostName', `n" ;
} | sort ) + "`t`"`$P.255.255`"`t=>`t'None'"
"<?php
`$P = '10.213';
`$IpHostArr = [`n`n$DnsDataTable`n];
?>" | Out-File -Encoding ASCII -FilePath IpHostLookups.php
Get-Content IpHostLookups.php
How can I extract a substring using PowerShell?
I have this string ...
"-----start-------Hello World------end-------"
I have to extract ...
Hello World
What is the best way to do that?
The -match operator tests a regex, combine it with the magic variable $matches to get your result
PS C:\> $x = "----start----Hello World----end----"
PS C:\> $x -match "----start----(?<content>.*)----end----"
True
PS C:\> $matches['content']
Hello World
Whenever in doubt about regex-y things, check out this site: http://www.regular-expressions.info
The Substring method provides us a way to extract a particular string from the original string based on a starting position and length. If only one argument is provided, it is taken to be the starting position, and the remainder of the string is outputted.
PS > "test_string".Substring(0,4)
Test
PS > "test_string".Substring(4)
_stringPS >
But this is easier...
$s = 'Hello World is in here Hello World!'
$p = 'Hello World'
$s -match $p
And finally, to recurse through a directory selecting only the .txt files and searching for occurrence of "Hello World":
dir -rec -filter *.txt | Select-String 'Hello World'
Not sure if this is efficient or not, but strings in PowerShell can be referred to using array index syntax, in a similar fashion to Python.
It's not completely intuitive because of the fact the first letter is referred to by index = 0, but it does:
Allow a second index number that is longer than the string, without generating an error
Extract substrings in reverse
Extract substrings from the end of the string
Here are some examples:
PS > 'Hello World'[0..2]
Yields the result (index values included for clarity - not generated in output):
H [0]
e [1]
l [2]
Which can be made more useful by passing -join '':
PS > 'Hello World'[0..2] -join ''
Hel
There are some interesting effects you can obtain by using different indices:
Forwards
Use a first index value that is less than the second and the substring will be extracted in the forwards direction as you would expect. This time the second index value is far in excess of the string length but there is no error:
PS > 'Hello World'[3..300] -join ''
lo World
Unlike:
PS > 'Hello World'.Substring(3,300)
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within
the string.
Backwards
If you supply a second index value that is lower than the first, the string is returned in reverse:
PS > 'Hello World'[4..0] -join ''
olleH
From End
If you use negative numbers you can refer to a position from the end of the string. To extract 'World', the last 5 letters, we use:
PS > 'Hello World'[-5..-1] -join ''
World
PS> $a = "-----start-------Hello World------end-------"
PS> $a.substring(17, 11)
or
PS> $a.Substring($a.IndexOf('H'), 11)
$a.Substring(argument1, argument2) --> Here argument1 = Starting position of the desired alphabet and argument2 = Length of the substring you want as output.
Here 17 is the index of the alphabet 'H' and since we want to Print till Hello World, we provide 11 as the second argument
Building on Matt's answer, here's one that searches across newlines and is easy to modify for your own use
$String="----start----`nHello World`n----end----"
$SearchStart="----start----`n" #Will not be included in results
$SearchEnd="`n----end----" #Will not be included in results
$String -match "(?s)$SearchStart(?<content>.*)$SearchEnd"
$result=$matches['content']
$result
--
NOTE: if you want to run this against a file keep in mind Get-Content returns an array not a single string. You can work around this by doing the following:
$String=[string]::join("`n", (Get-Content $Filename))
other solution
$template="-----start-------{Value:This is a test 123}------end-------"
$text="-----start-------Hello World------end-------"
$text | ConvertFrom-String -TemplateContent $template
Since the string is not complex, no need to add RegEx strings. A simple match will do the trick
$line = "----start----Hello World----end----"
$line -match "Hello World"
$matches[0]
Hello World
$result = $matches[0]
$result
Hello World
I needed to extract a few lines in a log file and this post was helpful in solving my issue, so i thought of adding it here. If someone needs to extract muliple lines, you can use the script to get the index of the a word matching that string (i'm searching for "Root") and extract content in all lines.
$File_content = Get-Content "Path of the text file"
$result = #()
foreach ($val in $File_content){
$Index_No = $val.IndexOf("Root")
$result += $val.substring($Index_No)
}
$result | Select-Object -Unique
Cheers..!