Getting a Windows command prompt contents to a text file - batch-file

I want to write a batch utility to copy the output of a command prompt window to a file. I run my command prompt windows with the maximum depth of 9999 lines, and occasionally I want to grab the output of a command whose output is off-screen. I can do this manually with the keys Ctrl-A, Ctrl-Cand then pasting the result into Notepad - I just want to automate it in a batch file with a call to:
SaveScreen <text file name>
I know I can do it with redirection, but that would involve knowing that I will need to save the output of a batch command sequence beforehand.
So if I had a batch script:
call BuildPhase1.bat
if "%ErrorLevel% gtr 0 goto :ErrorExit
call BuildPhase2.bat
if "%ErrorLevel% gtr 0 goto :ErrorExit
call BuildPhase3.bat
if "%ErrorLevel% gtr 0 goto :ErrorExit
I could write:
cls
call BuildPhase1.bat
if "%ErrorLevel% gtr 0 call SaveScreen.bat BuildPhase1.err & goto :ErrorExit
call BuildPhase2.bat
if "%ErrorLevel% gtr 0 call SaveScreen.bat BuildPhase2.err & goto :ErrorExit
call BuildPhase3.bat
if "%ErrorLevel% gtr 0 call SaveScreen.bat BuildPhase3.err & goto :ErrorExit
or I could just type SaveScreen batch.log when I see that a run has failed.
My experiments have got me this far:
<!-- : Begin batch script
#cscript //nologo "%~f0?.wsf" //job:JS
#exit /b
----- Begin wsf script --->
<package>
<job id="JS">
<script language="JScript">
var oShell = WScript.CreateObject("WScript.Shell");
oShell.SendKeys ("hi folks{Enter}") ;
oShell.SendKeys ("^A") ; // Ctrl-A (select all)
oShell.SendKeys ("^C") ; // Ctrl-C (copy)
oShell.SendKeys ("% ES") ; // Alt-space, E, S (select all via menu)
oShell.SendKeys ("% EY") ; // Alt-space, E, Y (copy via menu)
// ... invoke a notepad session, paste the clipboard into it, save to a file
WScript.Quit () ;
</script>
</job>
</package>
My keystrokes are making it to the command prompt so presumably I have the correct window focused - it just seems to be ignoring the Ctrl and Alt modifiers. It also recognises Ctrl-C but not Ctrl-A. Because it has ignored the Ctrl-A to select all the text, the Ctrl-C causes the batch file to think it has seen a break command.
I've seen the other answers like this one but they all deal with methods using redirection, rather than a way of doing it after the fact "on demand".
* UPDATE *
On the basis of #dxiv's pointer, here is a batch wrapper for the routine:
Get-ConsoleAsText.bat
:: save the contents of the screen console buffer to a disk file.
#set "_Filename=%~1"
#if "%_Filename%" equ "" #set "_Filename=Console.txt"
#powershell Get-ConsoleAsText.ps1 >"%_Filename%"
#exit /b 0
The Powershell routine is pretty much as was presented in the link, except that:
I had to sanitise it to remove some of the more interesting character substitutions the select/copy/paste operation introduced.
The original saved the trailing spaces as well. Those are now trimmed.
Get-ConsoleAsText.ps1
# Get-ConsoleAsText.ps1 (based on: https://devblogs.microsoft.com/powershell/capture-console-screen/)
#
# The script captures console screen buffer up to the current cursor position and returns it in plain text format.
#
# Returns: ASCII-encoded string.
#
# Example:
#
# $textFileName = "$env:temp\ConsoleBuffer.txt"
# .\Get-ConsoleAsText | out-file $textFileName -encoding ascii
# $null = [System.Diagnostics.Process]::Start("$textFileName")
#
if ($host.Name -ne 'ConsoleHost') # Check the host name and exit if the host is not the Windows PowerShell console host.
{
write-host -ForegroundColor Red "This script runs only in the console host. You cannot run this script in $($host.Name)."
exit -1
}
$textBuilder = new-object system.text.stringbuilder # Initialize string builder.
$bufferWidth = $host.ui.rawui.BufferSize.Width # Grab the console screen buffer contents using the Host console API.
$bufferHeight = $host.ui.rawui.CursorPosition.Y
$rec = new-object System.Management.Automation.Host.Rectangle 0,0,($bufferWidth - 1),$bufferHeight
$buffer = $host.ui.rawui.GetBufferContents($rec)
for($i = 0; $i -lt $bufferHeight; $i++) # Iterate through the lines in the console buffer.
{
$Line = ""
for($j = 0; $j -lt $bufferWidth; $j++)
{
$cell = $buffer[$i,$j]
$line = $line + $cell.Character
}
$line = $line.trimend(" ") # remove trailing spaces.
$null = $textBuilder.Append($line)
$null = $textBuilder.Append("`r`n")
}
return $textBuilder.ToString()

The contents of the console buffer can be retrieved with the PS script from PowerShell's team blog Capture console screen mentioned in a comment, now edited into OP's question.
The last line could also be changed to copy the contents to the clipboard instead of returning it.
Set-Clipboard -Value $textBuilder.ToString()
As a side note, the reasons for using a StringBuilder rather than direct concatenation are discussed in How does StringBuilder work internally in C# and How the StringBuilder class is implemented.

Related

How can I redirect the powershell output to my tcl-file?

Inside a .tcl file the batch file "test.ps1" is executed.
set output [exec test.ps1]
puts $output
Edit:
When I simply execute directly the test.ps1 file I can see all outputs in a shell-window. If I call the .tcl file I do not see the output only at the end when the batch file finished. All output text is written at the end but not updated when the ps1-file is still running.
What I see is that the application, where the .tcl file is called, goes to a "freeze" state so it is not possible to use that GUI as long the .ps1 file is running. At the end of the ps1 file all output is written at once and I can use the application again.
Question:
Is there a way to continously update the shell window in order to see the output?
You can execute the command in background using & and redirect its output to the tcl script output using >#stdout:
exec test.ps1 >#stdout &
Check the Tcl exec docs for details on how this works.
What about this:
################################################################################
##### Calls a single Powershell command (blocking, hidden)
### Arg: The command to give to Powershell via -command switch
### Ret: A List of three elements:
### -1 "" <errtext> -> error from twapi::create_process
### 0 <stdouttxt> "" -> Ok
### 1 "..." <stderrtext> -> Maybe Ok, something written to stderr
#
proc execPowershellCmd {cmd} {
set cmd "-command $cmd"
foreach chan {stdin stdout stderr} {
lassign [chan pipe] rd$chan wr$chan
}
if {[catch {
set cmd [string map [list \" \\\"] $cmd]; # muss noch in Wiki...
twapi::create_process [auto_execok powershell] -cmdline $cmd -showwindow hidden \
-inherithandles 1 -stdchannels [list $rdstdin $wrstdout $wrstderr]
} ret]} {
return [list -1 "" $ret]
}
chan close $wrstdin; chan close $rdstdin; chan close $wrstdout; chan close $wrstderr
foreach chan [list $rdstdout $rdstderr] {
chan configure $chan -encoding cp850 -blocking true; # -buffering full?; # -enc?
}
set out [read $rdstdout]; set err [read $rdstderr]
chan close $rdstdout; chan close $rdstderr
return [list [string compare $err ""] $out $err]
}

Looking for MORE/MOVE solutions that can handle files with more than 65534 rows

I have numerous uniquely named .CSV files that I need to remove the first 17 lines from. Some of these files exceed 65534 rows so my MORE/MOVE Batch script is not working. Looking for alternative solutions.
#echo off
for %%a in (*.csv) do (
more +17 "%%a" >"%%a.new"
move /y "%%a.new" "%%a" >nul
)
Regardless of number of rows input I am looking to have the 17 header rows removed and new file with all remaining rows built.
Here's a powershell option; this one uses a stream to cater for your large files:
$csvs = Get-ChildItem -Path "P:\ath to\your csvs" -Filter *.csv
foreach ( $csv in $csvs ) {
$fin = New-Object System.IO.StreamReader( $csv.FullName )
$fout = New-Object System.IO.StreamWriter( $csv.FullName+".new" )
try {
for( $s = 1; $s -le 17 -and !$fin.EndOfStream; $s++ ) {
$fin.ReadLine()
}
while( !$fin.EndOfStream ) {
$fout.WriteLine( $fin.ReadLine() )
}
}
finally {
$fout.Close()
$fin.Close()
}
}
Just change the path to your .csvs on the first line, before testing it.
I have purposely left out the deletion of the original files, simply appending .new to the new filenames to allow you time to check the results, test the speed etc. I will leave it to you to include a Rename/Delete or Move should you feel the need to extend the functionality.
Here's a one-line solution
for %%a in (*.txt) do powershell -Com "sc -Path '%%a' -Value (gc '%%a' | select -Skip 17)"
where gc and sc are default aliases for Get-Content and Set-Content respectively. See also
Powershell select-object skip multiple lines?
Powershell skip first 2 lines of txt file when importing it
If your files are huge then it'll be better to read in lines or blocks which can also be implemented easily using file functions, [IO.File]::OpenText or the -ReadCount option of Get-Content in PowerShell
Reading large text files with Powershell
Reading very BIG text files using PowerShell
How to process a file in PowerShell line-by-line as a stream
How can I make this PowerShell script parse large files faster?
As Squashman mentioned, for /f also has an option to skip lines at the beginning of the file
for %%a in (*.csv) do (
for /f "usebackq skip=17 delims=" %%l in ("%%f") do #echo(%%l>>"%%a.new"
move /y "%%a.new" "%%a" >nul
)
But that won't work if your file contains lines with special characters like & or |. For more information about it run for /?
Make your own cut command. This is VBScript ported to VB.NET.
Cut
cut {t|b} {i|x} NumOfLines
Cuts the number of lines from the top or bottom of file.
t - top of the file
b - bottom of the file
i - include n lines
x - exclude n lines
Example
cut t i 5 < "%systemroot%\win.ini"
Cut.bat
REM Cut.bat
REM This file compiles Cut.vb to Cut.exe
REM Cut.exe Removes specified from top or bottom of lines from StdIn and writes to StdOut
REM To use
REM cut {t|b} {i|x} NumOfLines
Rem Cuts the number of lines from the top or bottom of file.
Rem t - top of the file
Rem b - bottom of the file
Rem i - include n lines
Rem x - exclude n lines
Rem
Rem Example - Includes first 5 lines Win.ini
Rem
Rem cut t i 5 < "%systemroot%\win.ini"
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\vbc.exe" /target:exe /out:"%~dp0\Cut.exe" "%~dp0\Cut.vb" /verbose
pause
Cut.vb
'DeDup.vb
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports Microsoft.Win32
Public Module DeDup
Sub Main
Dim Arg() As Object
Dim RS as Object
Dim LineCount as Object
Dim Line as Object
Arg = Split(Command(), " ")
rs = CreateObject("ADODB.Recordset")
With rs
.Fields.Append("LineNumber", 4)
.Fields.Append("Txt", 201, 5000)
.Open
LineCount = 0
Line=Console.readline
Do Until Line = Nothing
LineCount = LineCount + 1
.AddNew
.Fields("LineNumber").value = LineCount
.Fields("Txt").value = Console.readline
.UpDate
Line = Console.ReadLine
Loop
.Sort = "LineNumber ASC"
If LCase(Arg(0)) = "t" then
If LCase(Arg(1)) = "i" then
.filter = "LineNumber < " & LCase(Arg(2)) + 1
ElseIf LCase(Arg(1)) = "x" then
.filter = "LineNumber > " & LCase(Arg(2))
End If
ElseIf LCase(Arg(0)) = "b" then
If LCase(Arg(1)) = "i" then
.filter = "LineNumber > " & LineCount - LCase(Arg(2))
ElseIf LCase(Arg(1)) = "x" then
.filter = "LineNumber < " & LineCount - LCase(Arg(2)) + 1
End If
End If
Do While not .EOF
Console.writeline(.Fields("Txt").Value)
.MoveNext
Loop
End With
End Sub
End Module

Is it possible to extract emails from a csv using a dos/cmd batch file?

I have a csv file with 1M email addresses and i need to extract them from the CSV to a text file.
I googled this and found very few links, and those that i found, didn't do the trick.
So is it even possible to extract emails from a csv using a dos/cmd batch file?
I now and it is possible to do it with Linux but saddly i'm have to use windows.
Set Arg = WScript.Arguments
set WshShell = createObject("Wscript.Shell")
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
'Remove ^ from quoting command line. Quote, ampersand and brackets
Pttn = Replace(Arg(2), "^(", "(")
Pttn = Replace(Pttn, "^)", ")")
Pttn = Replace(Pttn, "^&", "&")
Pttn = Replace(Pttn, "^""", """")
Set regEx1 = New RegExp
If Instr(LCase(Arg(1)), "i") > 0 then
regEx1.IgnoreCase = True
Else
regEx1.IgnoreCase = False
End If
regEx1.Global = False
regEx1.Pattern = Pttn
Do Until Inp.AtEndOfStream
Line=Inp.readline
Line = RegEx1.Replace(Line, Arg(3))
outp.writeline Line
Loop
To use
cscript //nologo "c:\path to\scriptname.vbs" < inputfile.txt > outputfile.txt
Replace
filter replace {i|n} expression replace
filter repl {i|n} expression replace
Finds and replaces text using regular expressions.
Also used to extract substrings from a file.
Ampersands and brackets in expression must be escaped with the caret. Do not escape carets. Use hexidecimal code \x22 for quotes.
SearchOptions
i - ignore case
n - none
Expression
Regular Expression Reference
Replace
The text to replace. Use $1, $2, $..., $n to specify sub matches in the replace string
Example
filter replace i "=" "No equal sign" < "%systemroot%\win.ini"
This searches for text within square brackets and replaces the line with cat followed by the text within brackets
Filter replace i "^\[^(.*^)\]" "cat$1" < %windir%\win.ini
This searches for any text and prints from the 11th character to the end of the line.
Filter replace i "^.{10}^(.*^)$" "$1" < %windir%\win.ini
This searches a CSV file and prints the second and fourth field
Filter replace i "^.+,^(.+^),.+,^(.+^)$" "$1,$2" < csv.txt
Try a RegEx like (there are thousands on the internet like http://www.regular-expressions.info/email.html)
[0-9a-zA-Z]+\.?[0-9a-zA-Z]?#[0-9a-zA-Z]+\.com|org|net|gov
#set #code=#Batch /*
#echo off
cscript //nologo //E:JScript "%~F0"
goto :EOF
#set #code=#JScript */
var fileContents = WScript.StdIn.ReadAll(),
search = /(\w+)#(\w+)\.(\w+)/g, match;
while ( match = search.exec(fileContents) ) {
WScript.Stdout.WriteLine(match[0]);
}
Copy previous code in a Batch file; for example: GetEmails.bat, and execute it redirecting the input/output files. This is the output of an example session:
C:\> type theFile.txt
Line, with, an, email, address, joedoe#unknown.org
Please, send, mail, to, george#contoso.com, and, someone#example.com, Thanks!
Line, number, 3, with, no, email, address
C:\> GetEmails.bat < theFile.txt
joedoe#unknown.org
george#contoso.com
someone#example.com

making a loop show as a counter in csh

Im currently working on a code in csh and when it gets to the loop for example it goes
loop end count=1
loop end count=2
loop end count=3
loop end count=4
loop end count=5 etc etc
Is there a way I can make it just say
loop end count=(number just counts up here without adding lines)
so I don't keep getting a wall of text when I run the code?
Thanks
if you are on a vt100 compatible terminal (most all linux terminals) you can generate appropriate terminal control codes:
echo -n "count is:"
echo -n "^[7" # save cursor position
set i = 0
while ( 1 )
# i = $i + 1
echo -n "^[[u^[[K" #move back to saved position and erase end of line
echo -n "i=" , $i
sleep 1
end
Note "^[" is the ASCII escape character, not caret-bracket. How you get that
in a script will depend on your editor.

Batch rename sequential files by padding with zeroes

I have a bunch of files named like so:
output_1.png
output_2.png
...
output_10.png
...
output_120.png
What is the easiest way of renaming those to match a convention, e.g. with maximum four decimals, so that the files are named:
output_0001.png
output_0002.png
...
output_0010.png
output_0120.png
This should be easy in Unix/Linux/BSD, although I also have access to Windows. Any language is fine, but I'm interested in some really neat one-liners (if there are any?).
Python
import os
path = '/path/to/files/'
for filename in os.listdir(path):
prefix, num = filename[:-4].split('_')
num = num.zfill(4)
new_filename = prefix + "_" + num + ".png"
os.rename(os.path.join(path, filename), os.path.join(path, new_filename))
you could compile a list of valid filenames assuming that all files that start with "output_" and end with ".png" are valid files:
l = [(x, "output" + x[7:-4].zfill(4) + ".png") for x in os.listdir(path) if x.startswith("output_") and x.endswith(".png")]
for oldname, newname in l:
os.rename(os.path.join(path,oldname), os.path.join(path,newname))
Bash
(from: http://www.walkingrandomly.com/?p=2850)
In other words I replace file1.png with file001.png and file20.png with file020.png and so on. Here’s how to do that in bash
#!/bin/bash
num=`expr match "$1" '[^0-9]*\([0-9]\+\).*'`
paddednum=`printf "%03d" $num`
echo ${1/$num/$paddednum}
Save the above to a file called zeropad.sh and then do the following command to make it executable
chmod +x ./zeropad.sh
You can then use the zeropad.sh script as follows
./zeropad.sh frame1.png
which will return the result
frame001.png
All that remains is to use this script to rename all of the .png files in the current directory such that they are zeropadded.
for i in *.png;do mv $i `./zeropad.sh $i`; done
Perl
(from: Zero pad rename e.g. Image (2).jpg -> Image (002).jpg)
use strict;
use warnings;
use File::Find;
sub pad_left {
my $num = shift;
if ($num < 10) {
$num = "00$num";
}
elsif ($num < 100) {
$num = "0$num";
}
return $num;
}
sub new_name {
if (/\.jpg$/) {
my $name = $File::Find::name;
my $new_name;
($new_name = $name) =~ s/^(.+\/[\w ]+\()(\d+)\)/$1 . &pad_left($2) .')'/e;
rename($name, $new_name);
print "$name --> $new_name\n";
}
}
chomp(my $localdir = `pwd`);# invoke the script in the parent-directory of the
# image-containing sub-directories
find(\&new_name, $localdir);
Rename
Also from above answer:
rename 's/\d+/sprintf("%04d",$&)/e' *.png
Fairly easy, although it combines a few features not immediately obvious:
#echo off
setlocal enableextensions enabledelayedexpansion
rem iterate over all PNG files:
for %%f in (*.png) do (
rem store file name without extension
set FileName=%%~nf
rem strip the "output_"
set FileName=!FileName:output_=!
rem Add leading zeroes:
set FileName=000!FileName!
rem Trim to only four digits, from the end
set FileName=!FileName:~-4!
rem Add "output_" and extension again
set FileName=output_!FileName!%%~xf
rem Rename the file
rename "%%f" "!FileName!"
)
Edit: Misread that you're not after a batch file but any solution in any language. Sorry for that. To make up for it, a PowerShell one-liner:
gci *.png|%{rni $_ ('output_{0:0000}.png' -f +($_.basename-split'_')[1])}
Stick a ?{$_.basename-match'_\d+'} in there if you have other files that do not follow that pattern.
I actually just needed to do this on OSX. Here's the scripts I created for it - single line!
> for i in output_*.png;do mv $i `printf output_%04d.png $(echo $i | sed 's/[^0-9]*//g')`; done
For mass renaming the only safe solution is mmv—it checks for collisions and allows renaming in chains and cycles, something that is beyond most scripts. Unfortunately, zero padding it ain't too hot at. A flavour:
c:> mmv output_[0-9].png output_000#1.png
Here's one workaround:
c:> type file
mmv
[^0-9][0-9] #1\00#2
[^0-9][0-9][^0-9] #1\00#2#3
[^0-9][0-9][0-9] #1\0#2#3
[^0-9][0-9][0-9][^0-9] #1\0#2#3
c:> mmv <file
Here is a Python script I wrote that pads zeroes depending on the largest number present and ignores non-numbered files in the given directory. Usage:
python ensure_zero_padding_in_numbering_of_files.py /path/to/directory
Body of script:
import argparse
import os
import re
import sys
def main(cmdline):
parser = argparse.ArgumentParser(
description='Ensure zero padding in numbering of files.')
parser.add_argument('path', type=str,
help='path to the directory containing the files')
args = parser.parse_args()
path = args.path
numbered = re.compile(r'(.*?)(\d+)\.(.*)')
numbered_fnames = [fname for fname in os.listdir(path)
if numbered.search(fname)]
max_digits = max(len(numbered.search(fname).group(2))
for fname in numbered_fnames)
for fname in numbered_fnames:
_, prefix, num, ext, _ = numbered.split(fname, maxsplit=1)
num = num.zfill(max_digits)
new_fname = "{}{}.{}".format(prefix, num, ext)
if fname != new_fname:
os.rename(os.path.join(path, fname), os.path.join(path, new_fname))
print "Renamed {} to {}".format(fname, new_fname)
else:
print "{} seems fine".format(fname)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
$rename output_ output_0 output_? # adding 1 zero to names ended in 1 digit
$rename output_ output_0 output_?? # adding 1 zero to names ended in 2 digits
$rename output_ output_0 output_??? # adding 1 zero to names ended in 3 digits
That's it!
with bash split,
linux
for f in *.png;do n=${f#*_};n=${n%.*};mv $f $(printf output_"%04d".png $n);done
windows(bash)
for f in *.png;do n=${f#*_};mv $f $(printf output_"%08s" $n);done
I'm following on from Adam's solution for OSX.
Some gotchyas I encountered in my scenario were:
I had a set of .mp3 files, so the sed was catching the '3' in the '.mp3' suffix. (I used basename instead of echo to rectify this)
My .mp3's had spaces within their names, E.g., "audio track 1.mp3", this was causing basename+sed to screw up a little bit, so I had to quote the "$i" parameter.
In the end, my conversion line looked like this:
for i in *.mp3 ; do mv "$i" `printf "track_%02d.mp3\n" $(basename "$i" .mp3 | sed 's/[^0-9]*//g')` ; done
Using ls + awk + sh:
ls -1 | awk -F_ '{printf "%s%04d.png\n", "mv "$0" "$1"_", $2}' | sh
If you want to test the command before runing it just remove the | sh
I just want to make time lapse movie using
ffmpeg -pattern_type glob -i "*.jpg" -s:v 1920x1080 -c:v libx264 output.mp4
and got a similar problem.
[image2 # 000000000039c300] Pattern type 'glob' was selected but globbing is not supported by this libavformat build
glob not support on Windows 7 .
Also if file list like below, and uses %2d.jpg or %02d.jpg
1.jpg
2.jpg
...
10.jpg
11.jpg
...
[image2 # 00000000005ea9c0] Could find no file with path '%2d.jpg' and index in the range 0-4
%2d.jpg: No such file or directory
[image2 # 00000000005aa980] Could find no file with path '%02d.jpg' and index in the range 0-4
%02d.jpg: No such file or directory
here is my batch script to rename flies
#echo off
setlocal enabledelayedexpansion
set i=1000000
set X=1
for %%a in (*.jpg) do (
set /a i+=1
set "filename=!i:~%X%!"
echo ren "%%a" "!filename!%%~xa"
ren "%%a" "!filename!%%~xa"
)
after rename 143,323 jpg files,
ffmpeg -i %6d.jpg -s:v 1920x1080 -c:v libx264 output.mp4

Resources