I am using Mp4box in order to split a file into one minute parts, normally Mp4box use file_nnn.mp4 where the n are like 001, 002, 003, ....
I would like to rename it to partn.mp4 where n is also an increasing odd number.
I use this code but it is not working.
Mp4box -split 60 file.mp4 | foreach-
object -begin {$c=1} -process rename-
item $_ -newname "part$c.mp4";
$c=$c+2 }
So lets talk about what you have wrong.
Mp4box -split 60 file.mp4 |
foreach-object -begin {$c=1} -process
rename-item $_ -newname "part$c.mp4";
$c=$c+2
}
This is not a valid powershell statement.
The beginning is fine but after the first pipe | you then use a incomplete foreach-object with the parameters -process and -begin but they are separated by the foreach block where you create a variable $c that cant be seen by the rest of the script because its scope is confined to the foreach. You then have a rename-item that is outside the pipe | and then try to use a piped variable $_ which will be null because it is outside the pipe |. Finally you add 2 to $c which is null becuase its outside the scope of the $c in the foreach. You also add closing bracket } when there is no opening bracket.
Here is a working script which fully depends on the output of Mp4box. If Mp4box is not a powershell command and is instead a executable then this will not work.
$C = 1
Mp4box -split 60 file.mp4 |
%{
rename-item $_ -newname "part$c.mp4"
$C += 2
}
Lets go over whats above. I call $C = 1 outside the foreach so its usable in the foreach scope.
I pipe | the output of Mp4box to a % which is shorthand for foreach-object .
Inside the % (foreach-object) brackets { } it renames the item $_ from the pipe |.
Then it adds 2 to c using shorthand for += which is the same as add to ($C = $C + 2)
Now again this purely relies on if the output of Mp4box.
Related
$ready = Read-Host "How many you want?: "
$i = 0
do{
(-join(1..12 | ForEach {((65..90)+(97..122)+(".") | % {[char]$_})+(0..9)+(".") | Get-Random}))
$i++
} until ($i -match $ready) Out-File C:/numbers.csv -Append
If I give a value of 10 to the script - it will generate 10 random numbers and shows it on pshell. It even generates new file called numbers.csv. However, it does not add the generated output to the file. Why is that?
Your Out-File C:/numbers.csv -Append call is a completely separate statement from your do loop, and an Out-File call without any input simply creates an empty file.[1]
You need to chain (connect) commands with | in order to make them run in a pipeline.
However, with a statement such as as a do { ... } until loop, this won't work as-is, but you can convert such a statement to a command that you can use as part of a pipeline by enclosing it in a script block ({ ... }) and invoking it with &, the call operator (to run in a child scope), or ., the member-access operator (to run directly in the caller's scope):
[int] $ready = Read-Host "How many you want?"
$i = 0
& {
do{
-join (1..12 | foreach {
(65..90 + 97..122 + '.' | % { [char] $_ }) +(0..9) + '.' | Get-Random
})
$i++
} until ($i -eq $ready)
} | Out-File C:/numbers.csv -Append
Note the [int] type constraint to convert the Read-Host output, which is always a string, to a number, and the use of the -eq operator rather than the text- and regex-based -match operator in the until condition; also, unnecessary grouping with (...) has been removed.
Note: An alternative to the use of a script block with either the & or . operator is to use $(...), the subexpression operator, as shown in MikeM's helpful answer. The difference between the two approaches is that the former streams its output to the pipeline - i.e., outputs objects one by one - whereas $(...) invariably collects all output in memory, up front.
For smallish input sets this won't make much of a difference, but the in-memory collection that $(...) performs can become problematic with large input sets, so the & { ... } / . { ... } approach is generally preferable.
Arno van Boven' answer shows a simpler alternative to your do ... until loop based on a for loop.
Combining a foreach loop with .., the range operator, is even more concise and expressive (and the cost of the array construction is usually negligible and overall still amounts to noticeably faster execution):
[int] $ready = Read-Host "How many you want?"
& {
foreach ($i in 1..$ready) {
-join (1..12 | foreach {
([char[]] (65..90 + 97..122)) + 0..9 + '.' | Get-Random
})
}
} | Out-File C:/numbers.csv -Append
The above also shows a simplification of the original command via a [char[]] cast that directly converts an array of code points to an array of characters.
In PowerShell [Core] 7+, you could further simplify by taking advantage of Get-Random's -Count parameter:
[int] $ready = Read-Host "How many you want?"
& {
foreach ($i in 1..$ready) {
-join (
([char[]] (65..90 + 97..122)) + 0..9 + '.' | Get-Random -Count 12
)
}
} | Out-File C:/numbers.csv -Append
And, finally, you could have avoided a statement for looping altogether, and used the ForEach-Object cmdlet instead (whose built-in alias, perhaps confusingly, is also foreach, but there'a also %), as you're already doing inside your loop (1..12 | foreach ...):
[int] $ready = Read-Host "How many you want?"
1..$ready | ForEach-Object {
-join (1..12 | ForEach-Object {
([char[]] (65..90 + 97..122)) + 0..9 + '.' | Get-Random
})
} | Out-File C:/numbers.csv -Append
[1] In Windows PowerShell, Out-File uses UTF-16LE ("Unicode") encoding by default, so even a conceptually empty file still contains 2 bytes, namely the UTF-16LE BOM. In PowerShell [Core] v6+, BOM-less UTF-8 is the default across all cmdlets, so there you'll truly get an empty (0 bytes) file.
Another way is to wrap the loop in a sub-expression and pipe it:
$ready = Read-Host "How many you want?: "
$i = 0
$(do{
(-join(1..12 | ForEach {((65..90)+(97..122)+(".") | % {[char]$_})+(0..9)+(".") | Get-Random}))
$i++
} until ($i -match $ready)) | Out-File C:/numbers.csv -Append
I personally avoid Do loops when I can, because I find them hard to read. Combining the two previous answers, I'd write it like this, because I find it easier to tell what is going on. Using a for loop instead, every line becomes its own self-contained piece of logic.
[int]$amount = Read-Host "How many you want?: "
& {
for ($i = 0; $i -lt $amount; $i++) {
-join(1..12 | foreach {((65..90)+(97..122)+(".") | foreach {[char]$_})+(0..9)+(".") | Get-Random})
}
} | Out-File C:\numbers.csv -Append
(Please do not accept this as an answer, this is just showing another way of doing it)
I have a working script that searches for files with a regular expression. The script returns 2 lines per file: the parent folder naĆ¹e, and the filename (matching the regex).
Get-ChildItem -Path "D:\test\" -Recurse -File |
Where-Object { $_.BaseName -match '^[0-9]+$' } |
ForEach-Object { $_.FullName -Split '\',-3,'SimpleMatch' } |
select -last 2 |
Out-File "D:\wim.txt"
A certain system needs to have the output on one line, concatenated with for example \ or a similar character. How can I achieve this please ?
Many thanks !
Get-ChildItem -Path D:\test -Recurse -File |
Where-Object { $_.BaseName -match '^[0-9]+$' } |
ForEach-Object { ($_.FullName -split '\\')[-2,-1] -join '\' } | #'
Out-File D:\wim.txt
($_.FullName -Split '\\')[-2,-1] extracts the last 2 components from the file path
and -join '\' joins them back together.
Note that, aside from the line-formatting issue, your original command does not work as intended, because | select -last 2 is applied to the overall output, not per matching file; thus, even if there are multiple matching files, you'll only ever get the parent directory and filename of the last matching file.
The command above therefore extracts the last 2 \-separated path components inside the ForEach-Object block, directly on the result of the -split operation, so that 2 (joined) components are returned per file.
As an aside, the -3 in $_.FullName -split '\', -3, 'SimpleMatch' does not extract the last 3 tokens; it is currently effectively treated the same as 0, meaning that all resulting tokens are returned; given that -split defaults to using regexes, and representing a literal \ requires escaping as \\, $_.FullName -split '\', -3, 'SimpleMatch' is the same as $_.FullName -split '\\', which is what the solution above uses.
Note that there is a green-lit -split enhancement that will give negative <Max-substrings> values new meaning in the future, applying the current positive-number logic analogously to the end of the input string; e.g, -3 would mean: return the last 2 components plus whatever is left of the input string before them (with the resulting tokens still reported from left to right).
I want to be able to split some text out of a txtfile:
For example:
Brackets#Release 1.11.6#Path-to-Brackets
Atom#v1.4#Path-to-Atom
I just want to have the "Release 1.11.6" part. I am doing a where-object starts with Brackets but I don't know the full syntax. Here is my code:
"Get-Content -Path thisfile.txt | Where-Object{$_ < IM STUCK HERE > !
You could do this:
((Get-Content thisfile.txt | Where-Object { $_ -match '^Brackets' }) -Split '#')[1]
This uses the -match operator to filter out any lines that don't start with Brackets (the ^ special regex character indicates that what follows must be at the beginning of the line). Then it uses the -Split operator to split those lines on # and then it uses the array index [1] to get the second element of the split (arrays start at 0).
Note that this will throw an error if the split on # doesn't return at least two elements and it assumes that the text you want is always the second of those elements.
$bracketsRelease = Get-Content -path thisfile.txt | foreach-object {
if ( $_ -match 'Brackets#(Release [^#]+)#' )
{
$Matches[1]
}
}
or
(select-string -Path file.txt -Pattern 'Brackets#(Release [^#]+)#').Matches[0].Groups[1].value
New to Powershell and looking for some help. I have multiple xyz files that are currently displaying a z value with positive up and I need to make all the values negative (or z-positive down). So far my best attempt has been to try to cobble together what I know from other lines of code, but I'm still far from a solution.
$data = Get-ChildItem "C:\Users\dwilson\Desktop\test" -Recurse |
foreach ($item in $data) {$item.'Col 3' = 0 - $item.'Col 3'}
Any help would be greatly appreciated!
Somewhat of an aside, but you're using mixed syntax here.
foreach ($i in $set) { ... }
This is the Foreach statement. It does not accept input from the pipeline, nor will it automatically send output down the pipeline. See also Get-Help about_Foreach.
Get-ChildItem | ForEach-Object { ... }
This is the ForEach-Object command. It accepts input from the pipeline and sends output down the pipeline. Critically, this command also has a default alias of foreach. See also Get-Help ForEach-Object.
The help for the Foreach statement explains how PowerShell decides what foreach means (emphasis mine):
When Foreach appears in a command pipeline, PowerShell uses the foreach alias, which calls the ForEach-Object command. When you use the foreach alias in a command pipeline, you do not include the ($<item> in $<collection>) syntax as you do with the Foreach statement. This is because the prior command in the pipeline provides this information.
Using the looping over the files returned by Get-ChildItem you can import each with Import-CSV using the -Header parameter to assign the property name for each column. Then we can update the information for that property then export it. Using the ConvertTo-CSV cmdlet and then using Select-Object -Skip 1 we can drop the header off the CSV before exporting it.
$Files = Get-ChildItem "C:\Users\dwilson\Desktop\test" -Recurse
foreach ($File in $Files) {
$Data = Import-CSV $File -Header 'Col1','Col2','Col3'
$newData = ForEach ($Row in $Data) {
$Row.'Col3' = 0 - $Row.'Col3'
$Row
}
$newData |
Convertto-CSV -NoTypeInformation |
Select-Object -Skip 1 |
Out-File "$($File.Fullname).new)"
}
I need to only search the 1st line and last line in a text file to find a "-" and remove it.
How can I do it?
I tried select-string, but I don't know to find the 1st and last line and only remove "-" from there.
Here is what the text file looks like:
% 01-A247M15 G70
N0001 G30 G17 X-100 Y-100 Z0
N0002 G31 G90 X100 Y100 Z45
N0003 ; --PART NO.: NC-HON.PHX01.COVER-SHOE.DET-1000.050
N0004 ; --TOOL: 8.55 X .3937
N0005 ;
N0006 % 01-A247M15 G70
Something like this?
$1 = Get-Content C:\work\test\01.I
$1 | select-object -index 0, ($1.count-1)
Ok, so after looking at this for a while, I decided there had to be a way to do this with a one liner. Here it is:
(gc "c:\myfile.txt") | % -Begin {$test = (gc "c:\myfile.txt" | select -first 1 -last 1)} -Process {if ( $_ -eq $test[0] -or $_ -eq $test[-1] ) { $_ -replace "-" } else { $_ }} | Set-Content "c:\myfile.txt"
Here is a breakdown of what this is doing:
First, the aliases for those now familiar. I only put them in because the command is long enough as it is, so this helps keep things manageable:
gc means Get-Content
% means Foreach
$_ is for the current pipeline value (this isn't an alias, but I thought I would define it since you said you were new)
Ok, now here is what is happening in this:
(gc "c:\myfile.txt") | --> Gets the content of c:\myfile.txt and sends it down the line
% --> Does a foreach loop (goes through each item in the pipeline individually)
-Begin {$test = (gc "c:\myfile.txt" | select -first 1 -last 1)} --> This is a begin block, it runs everything here before it goes onto the pipeline stuff. It is loading the first and last line of c:\myfile.txt into an array so we can check for first and last items
-Process {if ( $_ -eq $test[0] -or $_ -eq $test[-1] ) --> This runs a check on each item in the pipeline, checking if it's the first or the last item in the file
{ $_ -replace "-" } else { $_ } --> if it's the first or last, it does the replacement, if it's not, it just leaves it alone
| Set-Content "c:\myfile.txt" --> This puts the new values back into the file.
Please see the following sites for more information on each of these items:
Get-Content uses
Get-Content definition
Foreach
The Pipeline
Begin and Process part of the Foreach (this are usually for custom function, but they work in the foreach loop as well)
If ... else statements
Set-Content
So I was thinking about what if you wanted to do this to many files, or wanted to do this often. I decided to make a function that does what you are asking. Here is the function:
function Replace-FirstLast {
[CmdletBinding()]
param(
[Parameter( `
Position=0, `
Mandatory=$true)]
[String]$File,
[Parameter( `
Position=1, `
Mandatory=$true)]
[ValidateNotNull()]
[regex]$Regex,
[Parameter( `
position=2, `
Mandatory=$false)]
[string]$ReplaceWith=""
)
Begin {
$lines = Get-Content $File
} #end begin
Process {
foreach ($line in $lines) {
if ( $line -eq $lines[0] ) {
$lines[0] = $line -replace $Regex,$ReplaceWith
} #end if
if ( $line -eq $lines[-1] ) {
$lines[-1] = $line -replace $Regex,$ReplaceWith
}
} #end foreach
}#End process
end {
$lines | Set-Content $File
}#end end
} #end function
This will create a command called Replace-FirstLast. It would be called like this:
Replace-FirstLast -File "C:\myfiles.txt" -Regex "-" -ReplaceWith "NewText"
The -Replacewith is optional, if it is blank it will just remove (default value of ""). The -Regex is looking for a regular expression to match your command. For information on placing this into your profile check this article
Please note: If you file is very large (several GBs), this isn't the best solution. This would cause the whole file to live in memory, which could potentially cause other issues.
try:
$txt = get-content c:\myfile.txt
$txt[0] = $txt[0] -replace '-'
$txt[$txt.length - 1 ] = $txt[$txt.length - 1 ] -replace '-'
$txt | set-content c:\myfile.txt
You can use the select-object cmdlet to help you with this, since get-content basically spits out a text file as one huge array.
Thus, you can do something like this
get-content "path_to_my_awesome_file" | select -first 1 -last 1
To remove the dash after that, you can use the -Replace switch to find the dash and remove it. This is better than using System.String.Replace(...) method because it can match regex statements and replace whole arrays of strings too!
That would look like:
# gc = Get-Content. The parens tell Powershell to do whatever's inside of it
# then treat it like a variable.
(gc "path_to_my_awesome_file" | select -first 1 -last 1) -Replace '-',''
If your file is very large you might not want to read the whole file to get the last line. gc -Tail will get the last line very quickly for you.
function GetFirstAndLastLine($path){
return New-Object PSObject -Property #{
First = Get-Content $path -TotalCount 1
Last = Get-Content $path -Tail 1
}
}
GetFirstAndLastLine "u_ex150417.log"
I tried this on a 20 gb log file and it returned immediately. Reading the file takes hours.
You will still need to read the file if you want to keep all excising content and you want only to remove from the end. Using the -Tail is a quick way to check if it is there.
I hope it helps.
A cleaner answer to the above:
$Line_number_were_on = 0
$Awesome_file = Get-Content "path_to_ridiculously_excellent_file" | %{
$Line = $_
if ($Line_number_were_on -eq $Awesome_file.Length)
{ $Line -Replace '-','' }
else
{ $Line } ;
$Line_number_were_on++
}
I like one-liners, but I find that readability tends to suffer sometimes when I put terseness over function. If what you're doing is going to be part of a script that other people will be reading/maintaining, readability might be something to consider.
Following Nick's answer: I do need to do this on all text files in the directory tree and this is what I'm using now:
Get-ChildItem -Path "c:\work\test" -Filter *.i | where { !$_.PSIsContainer } | % {
$txt = Get-Content $_.FullName;
$txt[0] = $txt[0] -replace '-';
$txt[$txt.length - 1 ] = $txt[$txt.length - 1 ] -replace '-';
$txt | Set-Content $_.FullName
}
and it looks like it's working well now.
Simple process:
Replace $file.txt with your filename
Get-Content $file_txt | Select-Object -last 1
I was recently searching for comments in the last line of .bat files. It seems to mess up the error code of previous commands. I found this useful for searching for a pattern in the last line of files. Pspath is a hidden property that get-content outputs. If I used select-string, I would lose the filename. *.bat gets passed as -filter for speed.
get-childitem -recurse . *.bat | get-content -tail 1 | where { $_ -match 'rem' } |
select pspath
PSPath
------
Microsoft.PowerShell.Core\FileSystem::C:\users\js\foo\file.bat