Say I have a text file 123.txt
one,two,three
four,five,six
My goal is to capitalize the first character of each line by using Get-Culture. This is my attempt:
$str = gc C:\Users\Administrator\Desktop\123.txt
#Split each line into an array
$array = $str.split("`n")
for($i=0; $i -lt $array.Count; $i++) {
#Returns O and F:
$text = (Get-Culture).TextInfo.ToTitleCase($array[$i].Substring(0,1))
#Supposed to replace the first letter of each array with $text
$array[$i].Replace($array[$i].Substring(0,1), $text) >> .\Desktop\finish.txt
}
Result:
One,twO,three
Four,Five,six
I understand that .Replace() is replaces every occurrence of the current array, which is why I made sure that it's replacing ONLY the first character of the array with $array[$i].Substring(0,1), but this doesn't work.
Try the following:
Get-Content C:\Users\Administrator\Desktop\123.txt | ForEach-Object {
if ($_) {
$_.Substring(0, 1).ToUpper() + $_.Substring(1)
} else {
$_
}
} > .\Desktop\finish.txt
Get-Content reads the input file line by line and sends each line - stripped of its line terminator - through the pipeline.
ForEach-Object processes each line in the associated script block, in which $_ represents the line at hand:
if ($_) tests if the line is nonempty, i.e. if there's at least 1 character; if not, the else block simply passes the empty line through.
$_.Substring(0, 1).ToUpper() converts the line's 1st character to uppercase, implicitly using the current culture (with a single character, this is equivalent to applying Get-Culture).TextInfo.ToTitleCase()).
+ $_.Substring(1) appends the rest of the line.
Only > rater than >> is needed to write to the output file, because the entire pipeline's output is written at once.
The reason this is not working is because you are replacing the character...
$array[$i].Substring(0,1)
... but you are using the Replace method on the entire array element
$array[$i].Replace(...
Here the array element is a string, equal to a line of the input. So it will replace every occurrence of that character.
Get-Content (unless you use the -Raw parameter) by default returns the text as an array of strings. So you should be able to use this regex replace (I have used ToString().ToUpper() - nothing wrong with the Get-Culture method)
$str = gc C:\Users\Administrator\Desktop\123.txt
foreach($line in $str){
$line -replace '^\w', $line[0].ToString().ToUpper() >> .\Desktop\finish.txt
}
Regex explanation:
^ is an anchor. It specifies "the beginning of the string"
\w matches a word character - usually a-z, A-Z, 0-9
See mklement0's comments here for the more focused ^\p{Ll} and here for further explanation
Related
I want to print some lines after some match and before other different match.
Using awk I did it like this:
awk '/first match/{f=1} f; /second match/{f=0}'
Is any way to do it with PowerShell?
If you need to output the second matching line, you can do the following if reading from a file:
(Get-Content file).Where({$_ -match 'first'},'SkipUntil') | Foreach-Object {
if ($_ -match 'second') {
$_; break
}
else {
$_
}
}
If you have a variable ($content) with an array of lines, you can do the following:
$content.Where({$_ -match 'first'},'SkipUntil') | Foreach-Object {
if ($_ -match 'second') {
$_; break
}
else {
$_
}
}
Explanation:
The Where() method supports an expression and a mode. The expression, denoted by the script block {}, executes against each pipeline object. If your input is an array, $_ will contain the contents of each line.
-match performs a case-insensitive regular expression match by default.
The mode SkipUntil means do not output anything until the expression is true. Then continue outputting until all data is processed.
When the second match is found, that line will output and the remaining lines will stop being processed.
The basic workflow is the following:
Process the input contents in order starting at index 0 (line 1).
Capture nothing until the first match is found (expression evaluates to true).
Captured output is piped into a loop until the second match is found. The output includes the second match.
Once the second match is found, stop processing data.
Alternatively, if you do not need to include the second matched line, the code becomes more concise:
(gc file).Where({$_ -match 'first'},'SkipUntil').Where({$_ -match 'second'},'Until')
I need to add the quotation mark to a text file that contains 500 lines text.
The format is inconsistent. It has dashes, dots, numbers, and letters. For example
1527c705-839a-4832-9118-54d4Bd6a0c89
16575getfireshot.com.FireShotCaptureWebpageScreens
3EA2211E.GestetnerDriverUtility
I have tried to code this
$Flist = Get-Content "$home\$user\appfiles\out.txt"
$Flist | %{$_ -replace '^(.*?)', '"'}
I got the result which only added to the beginning of a line.
"Microsoft.WinJS.2.0
The expected result should be
"Microsoft.WinJS.2.0"
How to add quotation-mark to the end of each line as well?
There is no strict need to use a regex (regular expression) in your case (requires PSv4+):
(Get-Content $home\$user\appfiles\out.txt).ForEach({ '"{0}"' -f $_ })
Array method .ForEach() processes each input line via the script block ({ ... }) passed to it.
'"{0}"' -f $_ effectively encloses each input line ($_) in double quotes, via -f, the string-format operator.
If you did want to use a regex:
(Get-Content $home\$user\appfiles\out.txt) -replace '^|$', '"'
Regex ^|$ matches both the start (^) and the end ($) of the input string and replaces both with a " char., effectively enclosing the input string in double quotes.
As for what you tried:
^(.*?)
just matches the very start of the string (^), and nothing else, given that .*? - due to using the non-greedy duplication symbol ? - matches nothing else.
Therefore, replacing what matched with " only placed a " at the start of the input string, not also at the end.
You can use regex to match both:
The beginning of the line ^(.*?)
OR |
The End of the line $
I.e. ^(.*?)|$
$Flist = Get-Content "$home\$user\appfiles\out.txt"
$Flist | %{$_ -replace '^(.*?)|$', '"'}
I have a script I am running in Powershell, and I want to be able to put a line in my resulting text file output between the ccript name and the script content itself.
Currently, from the below, the line $str_msg = $file,[System.IO.File]::ReadAllText($file.FullName) is what I need, but I need a line to separate $file and the result of the next expression. How can I do this?
foreach ($file in [System.IO.Directory]::GetFiles($sqldir,"*.sql",
[System.IO.SearchOption]::AllDirectories))
{
$file = [System.IO.FileInfo]::new($file);
$Log.SetLogDir("");
$str_msg = $file,[System.IO.File]::ReadAllText($file.FullName);
$Log.AddMsg($str_msg);
Write-Output $str_msg;
# ...
}
$str_msg = $file,[System.IO.File]::ReadAllText($file.FullName) doesn't create a string, it creates a 2-element array ([object[]]), composed of the $file [System.IO.FileInfo] instance, and the string with the contents of that file.
Presumably, the .AddMsg() method expects a single string, so PowerShell stringifies the array in order to convert it to a single string; PowerShell stringifies an array by concatenating the elements with a single space as the separator by default; e.g.:
[string] (1, 2) yields '1 2'.
Therefore, it's best to compose $str_msg as a string to begin with, with an explicit newline as the separator, e.g.:
$strMsg = "$file`r`n$([System.IO.File]::ReadAllText($file.FullName))"
Note the use of escape sequence "`r`n" to produce a CRLF, the Windows-specific newline sequence; on Unix-like platforms, you'd use just "`n" (LF).
.NET offers a cross-platform abstraction, [Environment]::NewLine, which returns the platform-appropriate newline sequence (which you could alternatively embed as $([Environment]::NewLine) inside "...").
An alternative to string interpolation is to use -f, the string-formatting operator, which is based on the .NET String.Format() method:
$strMsg = '{0}{1}{2}' -f $file,
[Environment]::NewLine,
[System.IO.File]::ReadAllText($file.FullName)
Backtick-r+backtick-n will do a carriage return with a new line in PS. You could do a Get-Content of your $file variable as a new array variable, and insert the carriage return at a particular index:
Example file: test123.txt
If the file contents were this:
line1
line2
line3
Store the contents in an array variable so you have indices
[Array]$fileContent = Get-Content C:\path\to\test123.txt
To add a carriage return between line2 and line3:
$fileContent2 = $fileContent[0..1] + "`r`n" + $fileContent[2]
Then output a new file:
$fileContent2 | Out-File -FilePath C:\path\to\newfile.txt
You need to use the carriage return powershell special character, which is "`r".
Use it like this to add a carriage return in your line :
$str_msg = $file,"`r",[System.IO.File]::ReadAllText($file.FullName);
Check this documentation to have more details on Poewershell special characters.
I have the following case I'm trying to script in Powershell. I have done this exercise using Sed on a bash terminal, but having trouble writing in Powershell. Any help would be greatly appreciated.
(sed -r -e '/^N/h;/^[N-]/d;G;s/(.*)\n(.*)/\2 \1/' <file>, with a file format without < and > chars. surrounding the first letter on each line)
The start pattern always start with a <N> (only 1 instance per block), lines between start with a <J>, and the end pattern is always --
--------------
<N>ABC123
<J>SomethingHere1
<J>SomethingHere2
<J>SomethingHere3
-------------- <-- end of section
I'm trying to take the first line in each section <N> and copy it AFTER each <J> in the same section. For example:
<J>SomethingHere1 <N>ABC123
<J>SomethingHere2 <N>ABC123
<J>SomethingHere3 <N>ABC123
The number of <J> lines per section can vary (0-N). In a case with no <J>, nothing needs to be done.
Powershell version:5.1.16299.611
The following, pipeline-based solution isn't fast, but conceptually straightforward:
Get-Content file.txt | ForEach-Object {
if ($_ -match '^-+$') { $newSect = $true }
elseif ($newSect) { $firstSectionLine = $_; $newSect = $False }
else { "{0}`t{1}" -f $_, $firstSectionLine }
}
It reads and processes lines one by one (with the line at hand reflected in automatic variable $_.
It uses a regex (^-+) with the -match operator to identify section dividers; if found, flag $newSect is set to signal that the next line is the section's first data line.
If the first data line is hit, it is cached in variable $firstSectionLine, and the $newSect flag is reset.
All other lines are by definition the lines to which the first data line is to be appended, which is done via the -f string-formatting operator, using a tab char. (`t) as the separator.
Here's a faster PSv4+ solution that is more complex, however, and it reads the entire input file into memory up front:
((Get-Content -Raw file.txt) -split '(?m)^-+(?:\r?\n)?' -ne '').ForEach({
$firstLine, $otherLines = $_ -split '\r?\n' -ne ''
foreach ($otherLine in $otherLines) { "{0}`t{1}" -f $otherLine, $firstLine }
})
Get-Content -Raw reads in the input file in full, as a single string.
It uses the -split operator to split the input file into sections, and then processes each section.
Regex '(?m)^-+(?:\r?\n)?' matches a section divider line, optionally followed by a newline.
(?m) is the multiline option, which makes ^ and $ match the start and end of each line, respectively:
\r?\n matches a newline, either in CRLF (\r\n) or LF-only (\n) form.
(?:...) is a non-capturing group; making it non-capturing prevents what it matches from being included in the elements returned by -split.
-ne '' filters out resulting empty elements.
-split '\r?\n' splits each section into individual lines.
If performance is still a concern, you could speed up reading the file with [IO.File]::ReadAllText("$PWD/file.txt").
I have a text file with lines in this format:
FirstName,LastName,SSN,$x.xx,$x.xx,$x.xx
FirstName,MiddleInitial,LastName,SSN,$x.xx,$x.xx,$x.xx
The lines could be in either format. For example:
Joe,Smith,123-45-6789,$150.00,$150.00,$0.00
Jane,F,Doe,987-65-4321,$250.00,$500.00,$0.00
I want to basically turn everything before the SSN into a single field for the name thus:
Joe Smith,123-45-6789,$150.00,$150.00,$0.00
Jane F Doe,987-65-4321,$250.00,$500.00,$0.00
How can I do this using PowerShell? I think I need to use ForEach-Object and at some point replace "," with " ", but I don't know how to specify the pattern. I also don't know how to use a ForEach-Object with a $_.Where so that I can specify the "SkipUntil" mode.
Thanks very much!
Mathias is correct; you want to use the -replace operator, which uses regular expressions. I think this will do what you want:
$string -replace ',(?=.*,\d{3}-\d{2}-\d{4})',' '
The regular expression uses a lookahead (?=) to look for any commas that are followed by any number of any character (. is any character, * is any number of them including 0) that are then followed by a comma immediately followed by a SSN (\d{3}-\d{2}-\d{4}). The concept of "zero-width assertions", such as this lookahead, simply means that it is used to determine the match, but it not actually returned as part of the match.
That's how we're able to match only the commas in the names themselves, and then replace them with a space.
I know it's answered, and neatly so, but I tried to come up with an alternative to using a regex - count the number of commas in a line, then replace either the first one, or the first two, commas in the line.
But strings can't count how many times a character appears in them without using the regex engine(*), and replacements can't be done a specific number of times without using the regex engine(**), so it's not very neat:
$comma = [regex]","
Get-Content data.csv | ForEach {
$numOfCommasToReplace = $comma.Matches($_).Count - 4
$comma.Replace($_, ' ', $numOfCommasToReplace)
} | Out-File data2.csv
Avoiding the regex engine entirely, just for fun, gets me things like this:
Get-Content .\data.csv | ForEach {
$1,$2,$3,$4,$5,$6,$7 = $_ -split ','
if ($7) {"$1 $2 $3,$4,$5,$6,$7"} else {"$1 $2,$3,$4,$5,$6"}
} | Out-File data2.csv
(*) ($line -as [char[]] -eq ',').Count
(**) while ( #counting ) { # split/mangle/join }