Some Pexpect or Bash Expect analog - ssh

Have Nim some analog of bash expect or python pexpect?
Eg, can i catch ssh stdin by using something traditional?
P.S.1: i know about SSH2.nim library
P.S.2: expect not in bash on my end machine
P.S.3: eg, we writing easy program like
let a = readLine(stdin)
echo a
and other program running this and echo value to stdin.
Tnx.

Related

How do I nohup a here document in the background from within a ksh script?

I have a ksh script that comes to a point where it must run a long running command. The long running command is executed via a heredoc in the script presently. I want to throw the command (represented by cat in my samples below) into the background but only after taking its input from the heredoc. Since the "nohup cat.." finishes instantaneously and I see an empty nohup.out file, I am not sure the script is doing what I need it to do, which is to spawn a nohupped version of the heredoc command and exit, leaving the command to run for however long it takes to complete.
I am using cat as the "command" since it too sits there and just waits for console input.
Working version without nohupping:
#!/bin/ksh
cat << EOF
Hello
World
HOw are you!
EOF
Trying to nohup the heredoc:
#!/bin/ksh
nohup cat <<EOF
Hello
World
HOw are you!
EOF
Seems to work, output is going into nohup.out as expected. But now, how to throw that into the background? I tried the below (and many variations of it) :
#!/bin/ksh
nohup cat & <<EOF
Hello
World
HOw are you!
EOF
but, nohup.out is empty, so I am not sure what the above is doing. There is no running "cat" in the background which tells me it ran and completed at least - or maybe didn't run at all. No other variation I can invent for trying to throw the heredoc into the background from my ksh script works.
Any suggestions on a way to achieve this using heredoc?
Here are two options.
You could try wrapping the nohup sequence inside a function, which may look cleaner, and then invoking that function with the trailing ampersand.
Using a function, like this:
#!/bin/ksh
function dostuff
{
nohup cat <<- END
Hello
World
How are you!
END
}
dostuff &
wait
You can also try wrapping the commands to be backgrounded into a grouping { } block, separating each command with a ; inside the brackets, and then backgrounding that block via:
{ nohup cat <<- EOF
...
EOF
; whatever
} &

Execute any bash command, get the results of stdout/stderr immediatly and use stdin

I would like to execute any bash command. I found Command::new but I'm unable to execute "complex" commands such as ls ; sleep 1; ls. Moreover, even if I put this in a bash script, and execute it, I will only have the result at the end of the script (as it is explain in the process doc). I would like to get the result as soon as the command prints it (and to be able to read input as well) the same way we can do it in bash.
Command::new is indeed the way to go, but it is meant to execute a program. ls ; sleep 1; ls is not a program, it's instructions for some shell. If you want to execute something like that, you would need to ask a shell to interpret that for you:
Command::new("/usr/bin/sh").args(&["-c", "ls ; sleep 1; ls"])
// your complex command is just an argument for the shell
To get the output, there are two ways:
the output method is blocking and returns the outputs and the exit status of the command.
the spawn method is non-blocking, and returns a handle containing the child's process stdin, stdout and stderr so you can communicate with the child, and a wait method to wait for it to cleanly exit. Note that by default the child inherits its parent file descriptor and you might want to set up pipes instead:
You should use something like:
let child = Command::new("/usr/bin/sh")
.args(&["-c", "ls sleep 1 ls"])
.stderr(std::process::Stdio::null()) // don't care about stderr
.stdout(std::process::Stdio::piped()) // set up stdout so we can read it
.stdin(std::process::Stdio::piped()) // set up stdin so we can write on it
.spawn().expect("Could not run the command"); // finally run the command
write_something_on(child.stdin);
read(child.stdout);

How to store a command output in OpenVMS

Im having an issue writing a DCL in OpenVMS in that I need the DCL to call a command and capture its output (but not output the output to the screen) Later on in the DCL I then need to print that output I stored.
Heres an example:
ICE SET FASTER !This command sets my environment to the "Faster" environment.
The above command outputs this if executed directly in OpenVMS:
Initialising TEST Environment to FASTER
--------------------------------------------------------------------------------
Using Test Search rules FASTER
Using Test Search rules FASTER
--------------------------------------------------------------------------------
dcl>
So I created a DCL in an attempt to wrap this output in order to display a more simplified output. Heres my code so far:
!************************************************************************
$ !* Wrapper for setting ICE account. Outputs Environment
$ !************************************************************************
$ on error then goto ABORT_PROCESS
$ICE_DCL_MAIN:
$ ice set 'P1'
$ ICE SHOW
$ EXIT
$ABORT_PROCESS:
$ say "Error ICING to: " + P1
$ EXIT 2
[End of file]
In the lines above ICE SET 'P1' is setting the ice environment, but I dont want this output to be echoed to VMS. But what I do want is to write the output of $ICE SHOW into a variable and then echo that out later on in the DCL (most of which ive omitted for simplification purposes)
So what should be outputted should be:
current Test Environment is DISK$DEVELOPERS:[FASTER.DEVELOP]
Instead of:
Initialising TEST Environment to FASTER
--------------------------------------------------------------------------------
Using Test Search rules FASTER
Using Test Search rules FASTER
--------------------------------------------------------------------------------
current Test Environment is DISK$DEVELOPERS:[FASTER.DEVELOP]
Ive had a look through the manual and im getting a bit confused so I figured I tried here. Id appreciate any pointers. Thanks.
EDIT
Here is what ive come up with after the comments, the problem im having is when I connect to VMS using an emulator such as SecureCRT the correct output is echoed. But when I run the DCL via my SSH2 library in .NET it doesnt output anything. I guess thats because it closes the SYS$OUTPUT stream temporarily or something?
$ !************************************************************************
$ !* Wrapper for setting ICE account. Outputs Environment
$ !************************************************************************
$ on error then goto ABORT_PROCESS
$ICE_DCL_MAIN:
$ DEFINE SYS$OUTPUT NL:
$ ice set 'P1'
$ DEASSIGN SYS$OUTPUT
$ ice show
$ EXIT
$ABORT_PROCESS:
$ say "Error ICING to: " + P1
$ EXIT 2
[End of file]
EDIT 2
So I guess really I need to clarify what im trying to do here. Blocking the output doesnt so matter so much, im merely trying to capture it into a Symbol for example.
So in C# for example you can have a method that returns a string. So you'd have string myResult = vms.ICETo("FASTER"); and it would return that and store it in the variable.
I guess im looking for a similar thing in VMS so that once ive iced to the environment I can call:
$ environment == $ICE SHOW
But I of course get errors with that statement
The command $ assign/user_mode Thing Sys$Output will cause output to be redirected to Thing until you $ deassign/user_mode Sys$Output or next executable image exits. An assignment without the /USER_MODE qualifier will persist until deassigned.
Thing can be a logical name, a file specification (LOG.TXT) or the null device (NLA0:) if you simply want to flush the output.
When a command procedure is executed the output can be redirected using an /OUTPUT qualifier, e.g. $ #FOO/output=LOG.TXT.
And then there is piping ... .
You can redirect the output to a temp file and then print its content later:
$ pipe write sys$output "hi" > tmp.tmp
$ ty tmp.tmp
VMS is not Unix, DCL is not Bash: you can not easily set a DCL symbol from the output of a command.
Your ICE SHOW prints one line, correct? The first word is always "current", correct?
So you can create a hack.
First let me fake your ICE command:
$ create ice.com
$ write sys$output "current Test Environment is DISK$DEVELOPERS:[FASTER.DEVELOP]"
^Z
$
and I define a dcl$path pointing to the directory where this command procedure is
so that I can use/fake the command ICE
$ define dcl$path sys$disk[]
$ ice show
current Test Environment is DISK$DEVELOPERS:[FASTER.DEVELOP]
$
Now what you need, create a command procedure which sets a job logical
$ cre deflog.com
$ def/job/nolog mylog "current''p1'"
^Z
$
And I define a command "current" to run that command procedure:
$ current="#deflog """
Yes, you need three of the double quotes at the end of the line!
And finally:
$ pipe (ice show | #sys$pipe) && mysym="''f$log("mylog")'"
$ sh symb mysym
MYSYM = "current Test Environment is DISK$DEVELOPERS:[FASTER.DEVELOP]"
$
On the other hand, I don't know what you are referring to C# and Java. Can you elaborate on that and tell us what runs where?
You can try using: DEFINE /USER SYS$OUTPUT NL:.
It works only for the next command and you dont need to deassign.
Sharing some of my experience here. I used below methods to redirect outputs to files.
Define/Assign the user output and then execute the required command/script afterwards. Output will be written to .
$define /user sys$output <file_path>
execute your command/script
OR
assign /user <file_path> sys$output
execute your command/script
deassign sys$output
To re-direct in to null device like in Unix (mentioned in above answers), you can use 'nl:' instead of
define /user sys$output nl:
or
assign /user nl: sys$output

Equivalent to bash "expect" in powershell

I'm using powershell to run another powershell script, at some point the other script asks for some input, I would like to be able to read the output from the other script and based on that supply input to it. Similar to what you can do with expect on bash.
Any ideas?
Thanks
Just posting my solution so that it can help someone. I faced the same problem while running some other script that will ask for answers. First create a file "inputFileLocation.txt" with answers to each question in each line in sequence. Then run the script in below syntax. And it will do the work.
`cmd.exe /c "script.bat < inputFileLocation.txt"`
You just use Expect program in your powershell. It works. Powershell is a shell too, you can run code wrote by powershell, which call bash code, which call powershell again.
Bellow is a test, it passed.
It "can work with tcl expect" {
$bs = #'
echo "Do you wish to install this program?"
select yn in "Yes" "No"; do
case $yn in
Yes ) echo "install"; break;;
No ) exit;;
esac
done
'#
$bsf = New-TemporaryFile
$bs | Set-Content -Path $bsf
$tcls = #'
#!/bin/sh
# exp.tcl \
exec tclsh "$0" ${1+"$#"}
package require Expect
set timeout 100000
spawn {spawn-command}
expect {
"Enter password: $" {
exp_send "$password\r"
exp_continue
}
"#\? $" {
exp_send "1"
}
eof {}
timeout {}
}
'#
$tclf = New-TemporaryFile
$tcls -replace "{spawn-command}",("bash",$bsf -join " ") | Set-Content -Path $tclf
"bash", $tclf -join " " | Invoke-Expression
Remove-Item $bsf
Remove-Item $tclf
}
Let me explain the test.
create a bash file which expect an input.
create a tcl file which call bash created in step one.
invoke tcl program from powershell, it works, will not waiting for input.
Sample to solve part of the problem
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
Start-Process -FilePath C:\myexecbatchfile.bat
# Wait the application start for 2 sec
Start-Sleep -m 2000
# Send keys
[System.Windows.Forms.SendKeys]::SendWait("input1")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
Start-Sleep -m 3000
[System.Windows.Forms.SendKeys]::SendWait("input2")
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
I am not aware of any native capability to duplicate exact. This question has an answer that claims to be able to pass content to/from a process, so it might work with what you want.
How to run interactive commands in another application window from powershell
Good Luck!
Lee Holmes put out an "Expect for Powershell" in 2014 on the Powershell Gallery called Await. Turns out emulating expect is a lot more complicated than you'd imagine, involving the Win32 calls.
Package
https://www.powershellgallery.com/packages/Await/0.8
Demo
https://www.youtube.com/watch?v=tKyAVm7bXcQ

More efficient way of looping over SSH in KSH?

I currently have the following lines of code in a script:
set -A ARRAY OPTION1 OPTION2 OPTION3 OPTION4
set -A matches
for OPTION in ${ARRAY[#]}; do
DIFF=$(ssh $USER#$host " diff $PERSONALCONF $PRESETS$OPTION" )
if [[ $DIFF == "" ]]; then
set -A matches"${matches[#]}" $OPTION
fi
done
Basically, I have a loop that goes through each element in a pre-defined array, connects to a remote server (same server each time), and then compares a file with a file as defined by the loop using the diff command. Basically, it compares a personal.conf file with personal.conf.option1, personal.conf.option2, etc. If there is no difference, it adds it to the array. If there is a difference, nothing happens.
I was wondering if its possible to execute this or get the same result (storing the matching files in an array ON THE HOST MACHINE, not the server that's being connected to) by way of only connecting once via SSH. I cannot store anything on the remote server, nor can I execute a remote script on that server. I can only issue commands via ssh (kind of a goofy setup). Currently, it connects as many times as there are options. This seems inefficient. If anyone has a better solution I'd love to hear it.
Several options:
You can use OpenSSH multiplexing feature (see ssh(1)).
Also, most shells will gladly accept a script to run over stdin, so you could just run something like
cat script.sh | ssh $HOST /bin/sh
Most scripting languages (Perl, Python, Ruby, etc.) have some SSH module that allows connection reuse:
#!/usr/bin/perl
use Net::OpenSSH;
my ($user, $host) = (...);
my #options = (...);
my #matches;
my $ssh = Net::OpenSSH->new("$user\#$host");
for my $option (#options) {
my $diff = $ssh->capture("diff $personal_conf $presets$option");
if ($ssh->error) {
warn "command failed: " . $ssh->error;
}
else {
push #matches, $option if $diff eq '';
}
}
print "#matches\n";