How can I change the following code to look at all the .log files in the directory and not just the one file?
I need to loop through all the files and delete all lines that do not contain "step4" or "step9". Currently this will create a new file, but I'm not sure how to use the for each loop here (newbie).
The actual files are named like this: 2013 09 03 00_01_29.log. I'd like the output files to either overwrite them, or to have the SAME name, appended with "out".
$In = "C:\Users\gerhardl\Documents\My Received Files\Test_In.log"
$Out = "C:\Users\gerhardl\Documents\My Received Files\Test_Out.log"
$Files = "C:\Users\gerhardl\Documents\My Received Files\"
Get-Content $In | Where-Object {$_ -match 'step4' -or $_ -match 'step9'} | `
Set-Content $Out
Give this a try:
Get-ChildItem "C:\Users\gerhardl\Documents\My Received Files" -Filter *.log |
Foreach-Object {
$content = Get-Content $_.FullName
#filter and save content to the original file
$content | Where-Object {$_ -match 'step[49]'} | Set-Content $_.FullName
#filter and save content to a new file
$content | Where-Object {$_ -match 'step[49]'} | Set-Content ($_.BaseName + '_out.log')
}
To get the content of a directory you can use
$files = Get-ChildItem "C:\Users\gerhardl\Documents\My Received Files\"
Then you can loop over this variable as well:
for ($i=0; $i -lt $files.Count; $i++) {
$outfile = $files[$i].FullName + "out"
Get-Content $files[$i].FullName | Where-Object { ($_ -match 'step4' -or $_ -match 'step9') } | Set-Content $outfile
}
An even easier way to put this is the foreach loop (thanks to #Soapy and #MarkSchultheiss):
foreach ($f in $files){
$outfile = $f.FullName + "out"
Get-Content $f.FullName | Where-Object { ($_ -match 'step4' -or $_ -match 'step9') } | Set-Content $outfile
}
If you need to loop inside a directory recursively for a particular kind of file, use the below command, which filters all the files of doc file type
$fileNames = Get-ChildItem -Path $scriptPath -Recurse -Include *.doc
If you need to do the filteration on multiple types, use the below command.
$fileNames = Get-ChildItem -Path $scriptPath -Recurse -Include *.doc,*.pdf
Now $fileNames variable act as an array from which you can loop and apply your business logic.
Other answers are great, I just want to add... a different approach usable in PowerShell:
Install GNUWin32 utils and use grep to view the lines / redirect the output to file http://gnuwin32.sourceforge.net/
This overwrites the new file every time:
grep "step[49]" logIn.log > logOut.log
This appends the log output, in case you overwrite the logIn file and want to keep the data:
grep "step[49]" logIn.log >> logOut.log
Note: to be able to use GNUWin32 utils globally you have to add the bin folder to your system path.
Related
cd 'A:\P\E\D'
$files = Get-ChildItem . *.CSV -rec
ForEach ($file in $files) {
(Get-Content $file -Raw) | ForEach-Object {
*some simple code*
} | Set-Content $file
}
How to modify this powershell script to locate only files starting with letters A/a to O/o and ending with .csv in specified directory cd?
I thought the solution below would work, but the test file M_K_O_X.CSV stored in the cd directory was not found and modified. The solution above will find and modify the file. It's possible that I have the regex expression wrong or the problem is somewhere else? I tried also this regex -- "[A-O]..CSV"
cd 'A:\P\E\D'
$files = Get-ChildItem . -rec | Where-Object { $_.Name -like "[a-oA-O]*.*.CSV" }
ForEach ($file in $files) {
(Get-Content $file -Raw) | ForEach-Object {
*some simple code*
} | Set-Content $file
}
Looking at your wildcard pattern, seems like you have an extra *. that shouldn't be there:
'M_K_O_X.CSV' -like '[a-oA-O]*.*.CSV' # False
'M_K_O_X.CSV' -like '[a-oA-O]*.CSV' # True
In this case you could simply use the -Include Parameter which supports character ranges. Also PowerShell is case insensitive by default, [a-oA-O]*.CSV can be reduced to [a-o]*.CSV:
Get-ChildItem 'A:\P\E\D' -Recurse -Include '[a-o]*.csv' | ForEach-Object {
($_ | Get-Content -Raw) | ForEach-Object {
# *some simple code*
} | Set-Content -LiteralPath $_.FullName
}
As commented, I would use the standard wildcard -Filter to filter for all files with a .csv extension.
Then pipe to a Where-Object clause in which you can use regex -match
$files = Get-ChildItem -Path 'A:\P\E\D' -Filter '*.csv' -File -Recurse |
Where-Object { $_.Name -match '^[a-o]' }
foreach ($file in $files) {
# switch `-Raw` makes Get-Content return a single multiline string, so no need for a loop
$content = Get-Content -Path $file.FullName -Raw
# *some simple code manipulating $content*
$content | Set-Content -Path $file.FullName
}
However, if these are valid csv files, I would not recommend using a pure textual manipulation on them, instead use Import-Csv -Path $file.FullName and work on the properties on each of the objects returned.
In a directory, there are files with the following filenames:
ExampleFile.mp3
ExampleFile_pn.mp3
ExampleFile2.mp3
ExampleFile2_pn.mp3
ExampleFile3.mp3
I want to iterate through the directory, and IF there is a filename that contains the string '_pn.mp3', I want to test if there is a similarly named file without the '_pn.mp3' in the same directory. If that file exists, I want to remove it.
In the above example, I'd want to remove:
ExampleFile.mp3
ExampleFile2.mp3
and I'd want to keep ExampleFile3.mp3
Here's what I have so far:
$pattern = "_pn.mp3"
$files = Get-ChildItem -Path '$path' | Where-Object {! $_.PSIsContainer}
Foreach ($file in $files) {
If($file.Name -match $pattern){
# filename with _pn.mp3 exists
Write-Host $file.Name
# search in the current directory for the same filename without _pn
<# If(Test-Path $currentdir $filename without _pn.mp3) {
Remove-Item -Force}
#>
}
enter code here
You could use Group-Object to group all files by their BaseName (with the pattern removed), and then loop over the groups where there are more than one file. The result of grouping the files and filtering by count would look like this:
$files | Group-Object { $_.BaseName.Replace($pattern,'') } |
Where-Object Count -GT 1
Count Name Group
----- ---- -----
2 ExampleFile {ExampleFile.mp3, ExampleFile_pn.mp3}
2 ExampleFile2 {ExampleFile2.mp3, ExampleFile2_pn.mp3}
Then if we loop over these groups we can search for the files that do not end with the $pattern:
#'
ExampleFile.mp3
ExampleFile_pn.mp3
ExampleFile2.mp3
ExampleFile2_pn.mp3
ExampleFile3.mp3
'# -split '\r?\n' -as [System.IO.FileInfo[]] | Set-Variable files
$pattern = "_pn"
$files | Group-Object { $_.BaseName.Replace($pattern,'') } |
Where-Object Count -GT 1 | ForEach-Object {
$_.Group.Where({-not $_.BaseName.Endswith($pattern)})
}
This is how your code would look like, remove the -WhatIf switch if you consider the code is doing what you wanted.
$pattern = "_pn.mp3"
$files = Get-ChildItem -Path -Filter *.mp3 -File
$files | Group-Object { $_.BaseName.Replace($pattern,'') } |
Where-Object Count -GT 1 | ForEach-Object {
$toRemove = $_.Group.Where({-not $_.BaseName.Endswith($pattern)})
Remove-Item $toRemove -WhatIf
}
I think you can get by here by adding file names into a hash map as you go. If you encounter a file with the ending you are interested in, check if a similar file name was added. If so, remove both the file and the similar match.
$ending = "_pn.mp3"
$files = Get-ChildItem -Path $path -File | Where-Object { ! $_.PSIsContainer }
$hash = #{}
Foreach ($file in $files) {
# Check if file has an ending we are interested in
If ($file.Name.EndsWith($ending)) {
$similar = $file.Name.Split($ending)[0] + ".mp3"
# Check if we have seen the similar file in the hashmap
If ($hash.Contains($similar)) {
Write-Host $file.Name
Write-Host $similar
Remove-Item -Force $file
Remove-Item -Force $hash[$similar]
# Remove similar from hashmap as it is removed and no longer of interest
$hash.Remove($similar)
}
}
else {
# Add entry for file name and reference to the file
$hash.Add($file.Name, $file)
}
}
Just get a list of the files with the _pn then process against the rest.
$pattern = "*_pn.mp3"
$files = Get-ChildItem -Path "$path" -File -filter "$pattern"
Foreach ($file in $files) {
$TestFN = $file.name -replace("_pn","")
If (Test-Path -Path $(Join-Path -Path $Path -ChildPath $TestFN)) {
$file | Remove-Item -force
}
} #End Foreach
I have several csv and txt files in a directory with data in them. I need to truncate the data from all of these files but leave the header in each.
You can use following script - it should work, if all files have more than one line...
$files = dir .\* -include ('*.csv', '*.txt')
foreach ($file in $files) {
$firstline = (get-content $file)[0]
set-content $file -Value $firstline
}
You do not need to read the whole file in order to just capture the first line..
Get-ChildItem -Path 'D:\Test' -File | Where-Object { $_.Extension -match '\.(csv|txt)'} | ForEach-Object {
# only read the first line using -TotalCount
($_ | Get-Content -TotalCount 1) | Set-Content -Path $_.FullName
}
The above could produce empty or whitespace only files if the top line is empty or only contains whitespaces..
Perhaps then the best option to quickly truncate these files to the top NON-EMPTY line would be:
Get-ChildItem -Path 'D:\Test' -File | Where-Object { $_.Extension -match '\.(csv|txt)'} | ForEach-Object {
$newcontent = switch -Regex -File $_.FullName {
'\S' { $_ ; break} # output the first line that is not empty or whitespace-only and exit the switch
}
# write back to the file
$newcontent | Set-Content -Path $_.FullName
}
P.S. Using -Filter as parameter on Get-ChildItem would work faster, but unfortunately, the filter can only be used for ONE file pattern only, like '*.csv'.
If you need recursion (search subfolders as well), then you could user the -Include parameter which accepts an array of file patterns. However, for that to work, you also need to add switch -Recurse OR have the path end in \*.
-Include is not as fast as -Filter, just about the same speed as by using a Where-Object clause in the examples above
I've got a set of files in the following format
I'd like to change the file names to the following format
I've used the following code:
`$i = 21
dir | ForEach-Object {$_ | Rename-Item -NewName ('00816-101998-XX-A-2-50-{0:D3} Detalj.{1}' -f $i++, $_.Extension)}`
This code starts renaming with file name A.10.11.11
How do I get it to start from the first file name i.e. 8.A.10.11.8?
Thank you!
Looking at your question, I think this is what you intend to do.
The list of current filenames should be sorted by the integer value the filenames start with.
Next you want to rename them using a counter that starts with value 21
To do this, you can use a ForEach-Object loop, but the -NewName parameter of Rename-Item can also contain a scriptblock containing the action to perform.
$i = 21
(Get-ChildItem -Path 'D:\Test' -Filter '*.pdf' -File) |
Sort-Object #{Expression = {[int]($_.Name -split ' ')[0]}} |
Rename-Item -NewName { '00816-101998-XX-A-2-50-{0:D3} Detalj{1}' -f $script:i++, $_.Extension }
P.S. The image shows the filename starts with a number followed by a space, while in the question you give an example where the number is followed by a dot .. If that is the case in you file names, change [int]($_.Name -split ' ')[0] into [int]($_.Name -split '\.')[0]
1. We need to address and increment the value of the counter $i using the $script:i scoping syntax, otherwise $i does not exist in the NewName scriptblock
2. To avoid renaming files twice, enclose the Get-ChildItem part in the code in between brackets
param(
$directory = "d:\tmp",
[regex]$rx = "(?<=8.A.10.11.)\d+",
[string]$newPrefix = "00816-101998-XX-A-2-50-"
)
<#
8..20 | ForEach-Object {
$filePath = Join-Path $directory -ChildPath "8.A.10.11.$_.pdf"
Out-File -InputObject "some text" -FilePath $filePath -Encoding default
}
#>
Get-ChildItem $directory | ForEach-Object{
Rename-Item $_.FullName -NewName "$newPrefix$((($rx.Matches($_.BaseName)).value).padleft(3,'0'))$($_.Extension)"
}
I need to check and return files that exist in the filesystem, but are not listed in a given text file. For instance, my text file (sample.txt) will contain paths like:
\\SharedDrive\Data\DevS\Common\app_name\subfolder1\Archive\Archive1.vbproj
\\SharedDrive\Data\DevS\NotCommon\app_name\subfolder1\user\WebApp.vbproj
\\SharedDrive\Data\DevS\UnCommon\app_name\subfolder1\Manager\Managerial.vbproj
It happens that there are VB project files that exists on the drive but are not among the list, which i want to return along with their full path. For instance:
\\SharedDrive\Data\DevS\Common\app_name\subfolder2\Windows\SharedArchive.vbproj
\\SharedDrive\Data\DevS\NotCommon\app_name2\subfolder1\user2\WebApp2.vbproj
I tried this:
$log = "e:\pshell\notExists.log"
Get-Content "e:\pshell\Sample.txt" | Where-Object {
#Keep only paths that does not exists
!(Test-Path $_)
} | Set-Content $log
but this does the other way around.
Try this:
$baseDir = "\\SharedDrive\Data\DevS"
$log = "..."
$paths = Get-Content sample.txt
Get-ChildItem $baseDir -Recurse -Filter *.vbproj | ? {
-not $_.PSIsContainer -and $paths -notcontains $_.FullName
} | % { $_.FullName } | Set-Content $log