Is there a way to read stdin without blocking script's execution using a vbscript? - batch-file

I am trying to find a way read stdin without blocking my vbscript's execution but still no luck.
What I want to achieve is the following (written in sh shell script):
for i in {1..3}; do
read input;
echo $input;
sleep 1;
if [ "$input" == "done" ]; then
echo "process done";
exit;
fi
done
Tried the following in vbscript but script hangs in the first iteration waiting for Enter in order to proceed
input=""
for i=1 to 3
WScript.Echo i
WScript.sleep (100);
If WScript.StdIn.AtEndOfStream Then
input = input & WScript.StdIn.Readline()
If input = "done" Then
WScript.Echo "process done"
End if
End If
Next
Is there a way not to block my script while reading stdin?

Related

Getting a Windows command prompt contents to a text 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.

Shell - Taking the input from the user from each line

I'm trying to take the input from the user into an array but Shell is accepting user input separated by spaced. Is there any way to accept the user input given separately in each line. My code below:
#!/bin/bash
echo "enter the servers names..."
read -a array
for i in "${array[#]}"
do
echo $i
done
exit 0
Input:
hello world
I want to the input to be taken as below (in two different lines):
hello
world
Kindly help. Thanks.
You can specify the delimiter of read to stop reading, rather than newline. (read man page)
-d delim continue until the first character of DELIM is read, rather
than newline
For example:
read -d':' -a array
If you want there is no delimiter to input, you can use a loop to read into the elements of the array, then check whether the input is null string or not.
i=0
read "array[$i]"
while [ "${array[$i]}" ];
do
let "i = i+1"
read "array[$i]"
done
So the input will be
hello
world
# > there is one more empty line
According to help read:
Reads a single line from the standard input, or from file descriptor FD...
This loop would do instead:
echo "enter the servers names..."
i=0
until false; do
read server
[ -z "$server" ] && break
let i=i+1
array[$i]="$server"
done
for i in "${array[#]}"; do
echo $i
done
exit 0
The loop will exit on an empty line or EOF (ctrl-D).
Example session terminated by empty line:
#server:/tmp$ ./test.sh
enter the servers names...
se1
se2
se3
se1
se2
se3
Example session terminated by EOF on the empty line after se2:
#server:/tmp$ ./test.sh
enter the servers names...
se1
se2
se1
se2
Please note that I check for an empty string while reading the names; but it is possible to check for empty strings (or whatever else) in any loop, for example while printing them or doing other computations.
#!/bin/bash
echo "enter the servers names..."
read -a array -d '\n'
for i in "${array[#]}"
do
echo $i
done
exit 0
or
#!/bin/bash
echo "enter the servers names..."
readarray array
for i in "${array[#]}"
do
echo $i
done
exit 0

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.

Is it possible for sshkit capture to not error when the command executed returns nothing

What I'm trying to achieve is a capistrano3 task that does a log file grep on all servers - this would save a lot of time as we have a lot of servers so doing it manually or even scripted but sequentially takes ages.
I have a rough at the edges task that actually works except when one of the servers returns nothing for the grep. In this case the whole command falls over.
Hence wondering if there is a way to set capture to accept empty returns.
namespace :admin do
task :log_grep, :command, :file do |t,args|
command = args[:command] || 'ask for a command'
file = args[:file] || 'log_grep_results'
outs = {}
on roles(:app), in: :parallel do
outs[host.hostname] = capture(:zgrep, "#{command}")
end
File.open(file, 'w') do |fh|
outs.each do |host,out|
fh.write(out)
end
end
end
end
Should anyone else come to this question, here's solution - raise_on_non_zero_exit: false
i wanted:
resp = capture %([ -f /var/run/xxx/xxx.pid ] && echo "ok")
error:
SSHKit::Command::Failed: [ -f /var/run/xxx/xxx.pid ] && echo "ok" exit status: 1
[ -f /var/run/xxx/xxx.pid ] && echo "ok" stdout: Nothing written
[ -f /var/run/xxx/xxx.pid ] && echo "ok" stderr: Nothing written
solution:
resp = capture %([ -f /var/run/xxx/xxx.pid ] && echo "ok"), raise_on_non_zero_exit: false
# resp => ""
So the work around I did was to start adding what I'm calling Capistrano utility scripts in the repo. Then capistrano runs these scripts. All the scripts are is a wrapper around a grep and some logic to output something if the return is empty.
Capistrano code:
namespace :utils do
task :log_grep, :str, :file, :save_to do |t,args|
command_args = "#{args[:str]} #{args[:file]}"
outs = {}
on roles(:app), in: :parallel do
outs[host.hostname] = capture(:ruby, "#{fetch(:deploy_to)}/current/bin/log_grep.rb #{args[:str]} #{args[:file]}")
end
file = args[:save_to]
file ||= 'log_grep_output'
File.open(file, 'w') do |fh|
outs.each do |host,out|
s = "#{host} -- #{out}\n"
fh.write(s)
end
end
end
end
Ruby script log_grep.rb:
a = `zgrep #{ARGV[0]} #{ARGV[1]}`
if a.empty?
puts 'Nothing Found'
else
puts a
end

append a string in a file via tcl

i want to open up a pre-existed file and want to add a string inside the file one line before it sees the word 'exit' inside the file. the word 'exit' will always be the last line inside the file, so we can also see this as " add the string one line above the last line" problem. in other words, I want to append this string inside the file. here is example
Example.tcl (before)
AAAAAAA
BBBBBBB
CCCCCC
exit
Example.tcl (after)
AAAAAAA
BBBBBBB
CCCCCC
new_word_string
exit
Any suggestions are most welcome.
Working code:
Open the file for reading, and also open a temporary file:
set f1 [open $thefile]
set f2 [file tempfile]
Read one line at a time until all lines have been read. Look at the line. If it is the string "exit", print the new string to the temporary file. The write the line you read to the temporary file.
while {[set line [chan gets $f1]] ne {}} {
if {$line eq "exit"} {
chan puts $f2 $thestring
}
chan puts $f2 $line
}
Close the file and reopen it for reading.
chan close $f1
set f1 [open $thefile w]
Rewind the temporary file to the start position.
chan seek $f2 0
Read the entire contents of the temporary file and print them to the file.
chan puts -nonewline $f1 [chan read -nonewline $f2]
Close both files.
chan close $f1
chan close $f2
And we're done.
You could use a string buffer instead of a temporary file with minimal changes, to wit:
set f [open $thefile]
set tempstr {}
while {[set line [chan gets $f]] ne {}} {
if {$line eq "exit"} {
append tempstr $thestring\n
}
append tempstr $line\n
}
chan close $f
set f [open $thefile w]
chan puts -nonewline $f $tempstr
chan close $f
Documentation: append, chan, if, open, set, while
You could farm the work out to an external command (Tcl was written as a glue language after all):
% exec cat example.tcl
AAAAAAA
BBBBBBB
CCCCCC
exit
% set new_line "this is the new line inserted before exit"
this is the new line inserted before exit
% exec sed -i "\$i$new_line" example.tcl
% exec cat example.tcl
AAAAAAA
BBBBBBB
CCCCCC
this is the new line inserted before exit
exit

Resources