Powershell equivalent of "$#" from bash - bash

I don't know much about powershell, but I would like to run another script from powershell, and also pass along all the unused arguments. In bash I am doing it this way (simplified):
MY_PARAM=$1
shift
python otherscript.py "$#"
I have tried many things in powershell but nothing worked. In my python script I either received "System.Object[]" (or "System.Collections.Generic.List`1[System.Object]") or all the arguments wrapped in a single string as the first argument (or various error messages of course). I have tried:
python otherscript.py $args
Invoke-Expression "python otherscript.py $args"
And in place of $args I have also tried using $MyInvocation.Line or $MyInvocation.UnboundArguments
What is the proper syntax for this?
Update 1
As Mathias R. Jessen comments, invoking the python script from the "global scope" with python otherscript.py $args works as I would expect.
What I am actually trying to do, is call my python script from within a function:
param (
[string]$command = ""
)
function Invoke-Other() {
python otherscript.py $args
}
switch ($command) {
"foo" {
Invoke-Other $args
}
}
This is the setup, when I get a single, "wrapped" argument "bar baz", when I call my powershell script with .\myscript.ps1 foo bar baz.

When you call Invoke-Other $args the argument list is passed as a single (array) argument, so all script arguments end up as a nested array in $args[0] inside the function. You can verify that by checking the value of $args.Count inside and outside the function. The nested array then gets mangled into a string when you call the Python script with the argument list of the function.
Use splatting to have the argument list of the script passed as individual arguments to the function:
Invoke-Other #args

Related

Bash function variable command not found error

I have a bash script like this with a function
_launch()
{
${1}
}
testx()
{
_launch "TESTX=1 ls -la"
}
testx
I get error "TESTX=1 command not found" in _launch function. Why?
When I run TESTX=1 ls -la directly on shell it works fine.
It's not a good idea to use variables to hold commands. See BashFAQ/050
As long as you are dealing with executables and not shell built-ins, you could do this:
_launch() {
env $1
}
This won't play well in case you have literal spaces in values used in var=value pairs or arguments to the command being launched.
You can overcome this problem by just passing the command to the launch function and setting your variables in function invocation itself, like this:
_launch() {
# your launch prep steps here...
"$#" # run the command
# post launch code here
}
TESTX=1 TESTY=2 TESTZ=3 _launch ls -la
The variables would be passed down to the launched command as environment variables.
You get the error because first looks at the statement to see whether we have a variable assignment, and then does parameter expansion. In your case, bash doesn't recognize that you want to extend the environment for your ls command, and treats TESTX=1 as command to be executed.
For the same reason, the following does not set the bash variable ABC:
x='ABC=55'
$x
This would print ABC=55: command not found.

Does a Shell function run in a sub-shell?

I'm trying to get around a problem that seems to me you cannot pass open db2 connection to a sub-shell.
My code organization is as follows:
Driver script (in my_driver.sh)
# foo.sh defines baz() bar(), which use a db2 connection
# Also the "$param_file" is set in foo.sh!
source foo.sh
db2 "connect to $dbName USER $dbUser using $dbPass"
function doit
{
cat $param_file | while read params
do
baz $params
bar $params
done
}
doit
I've simplified my code, but the above is enough the give the idea. I start the above:
my_driver.sh
Now, my real issue is that the db2 connection is not available in sub-shell:
I tried:
. my_driver.sh
Does not help
If I do it manually from the command line:
source foo.sh
And I set $params manually:
baz $params
bar $params
Then it does work! So it seems that doit or something else acts as if bar and baz are executed from a sub-shell.
I would be elated if I can somehow figure out how to pass db2 open connection to sub-shell would be best.
Otherwise, these shell functions seem to me that they run in a sub-shell. Is there a way around that?
The shell does not create a subshell to run a function.
Of course, it does create subshells for many other purposes, not all of which might be obvious. For example, it creates subshells in the implementation of |.
db2 requires that the all db2 commands have the same parent as the db2 command which established the connection. You could log the PID using something like:
echo "Execute db2 from PID $$" >> /dev/stderr
db2 ...
(as long as the db2 command isn't execute inside a pipe or shell parentheses.)
One possible problem in the code shown (which would have quite a different symptom) is the use of the non-standard syntax
function f
To define a function. A standard shell expects
f()
Bash understands both, but if you don't have a shebang line or you execute the scriptfile using the sh command, you will end up using the system's default shell, which might not be bash.
Found solution, but can't yet fully explain the problem ....
if you change doit as follows it works!
function doit
{
while read params
do
baz $params
bar $params
done < $param_file
}
Only, I'm not sure why? and how I can prove it ...
If I stick in debug code:
echo debug check with PID=$$ PPID=$PPID and SHLVL=$SHLVL
I get back same results with the | or not. I do understand that cat $param_file | while read params creates a subshell, however, my debug statements always show the same PID and PPID...
So my problem is solved, but I'm missing some explanations.
I also wonder if this question would not be more well suited in the unix.stackexchange community?
A shell function in such shells as sh (i.e. Dash) or Bash may be considered as a labelled commands group or named "code block" which may be called multiple times by its name. A command group surrounded by {} does not create a subshell or "fork" a process, but executes in the same process and environment.
Some might find it relatively similar to goto where function names represent labels as in other programming languages, including C, Basic, or Assembler. However, the statements vary quite greatly (e.g. functions return, but goto - doesn't) and Go To Statement may be Considered Harmful.
Shell Functions
Shell functions are a way to group commands for later
execution using a single name for the group. They are executed just
like a "regular" command. When the name of a shell function is used as
a simple command name, the list of commands associated with that
function name is executed. Shell functions are executed in the current
shell context; no new process is created to interpret them.
Functions are declared using this syntax:
fname () compound-command [ redirections ]
or
function fname [()] compound-command [ redirections ]
This defines a shell function named fname. The reserved word function is optional. If the function reserved word is supplied, the parentheses are optional.
Source: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html or man bash.
Grouping Commands Together
Commands may be grouped by writing either
(list)
or
{ list; }
The first of these executes the commands in a subshell. Builtin commands grouped into a (list) will not affect the current shell. The
second form does not fork another shell so is slightly more efficient.
Grouping commands together this way allows you to redirect their
output as though they were one program:
{ printf " hello " ; printf " world\n" ; } > greeting
Note that "}" must follow a control operator (here, ";") so that it is recognized as a reserved word and not as another command
argument.
Functions
The syntax of a function definition is
name () command
A function definition is an executable statement; when executed it installs a function named name and returns an exit status of zero. The command is normally a list enclosed between "{" and "}".
Source: https://linux.die.net/man/1/dash or man sh.
Transfers control unconditionally.
Used when it is otherwise impossible to transfer control to the desired location using other statements... The goto statement transfers control to the location specified by label. The goto statement must be in the same function as the label it is referring, it may appear before or after the label.
Source: https://en.cppreference.com/w/cpp/language/goto
Goto
... It performs a one-way transfer of control to another line of code; in
contrast a function call normally returns control. The jumped-to
locations are usually identified using labels, though some languages
use line numbers. At the machine code level, a goto is a form of
branch or jump statement, in some cases combined with a stack
adjustment. Many languages support the goto statement, and many do not...
Source: https://en.wikipedia.org/wiki/Goto
Related:
https://mywiki.wooledge.org/BashProgramming#Functions
https://uomresearchit.github.io/shell-programming-course/04-subshells_and_functions/index.html (Subshells and Functions...)
Is there a "goto" statement in bash?
What's the difference between "call" and "invoke"?
https://en.wikipedia.org/wiki/Call_stack
https://mywiki.wooledge.org/BashPitfalls

Execute a passed alias inside a function?

Background:
I'm trying make a function that runs commands on a set interval because I don't have access to a "watch" program. Simplified to it's most basic from, the function I'm trying to write is runit() { $1; }.
What works:
This works fine and dandy when I pass it things that aren't aliases. For example, runit "ls -l" works fine. I get the full output from the ls -l command.
What doesn't work:
The problem starts when I pass it an alias. For example, setting alias ll="ls -l" then calling runit "ll" will result in -bash: ll: command not found.
Things I have tried:
When I hard-code the alias runit() { ll; }, it works fine and gives me what I expect.
I feel like I might be overlooking something, but I can't quite place my finger on it.
Why would hard-coding the alias work fine, but passing it into the function fail?
Is there a way to accomplish what I'm attempting to do?
From the bash man page discussion of aliases (emphases mine):
Aliases are expanded when a command is read, not when it is executed.
Therefore, an
alias definition appearing on the same line as another command does not take effect until the next line of input is read. The
commands
following the alias definition on that line are not affected by the new alias. This behavior is also an issue when functions are
executed. Aliases are expanded when a function definition is read, not when the function is executed, because a function
definition is
itself a compound command. As a consequence, aliases defined in a function are not available until after that function is executed.
To
be safe, always put alias definitions on a separate line, and do not use alias in compound commands.
You can observe this effect in functions by using the type command:
$ run_it () { ll; }
$ type run_it
You should see that the body of the function contains a call to ls -l, not ll.
The last sentence of the section on aliases:
For almost every purpose, aliases are superseded by shell functions.
My interpretation of that line is: if you think you want to use an alias, try writing a function first. Don't use an alias unless the function demonstrably fails to do what you need.
You can use eval like this:
$ runit() { eval $1; }
$ alias ll="ls -l"
$ runit "ll"
eval will expand any alias in $1 before the execution.
One way to solve this problem is to define a shell function rather than an alias.
ll () {
ls -l "$#"
}
The alias is expanded as a macro on command input, whereas the shell function is matched when the command is executed. This is a perfect example of how the shell's macro processor language is good for interactive grace but rather complicates actual programming.

Call ruby script from powershell

i have following situation:
a powershell script with this variables:
$RUBY = "C:\installing\ruby\bin\ruby.exe"
$ARGS = "C:\files\sript.rb $Arg1 $Arg2 $Arg3"
in a batch file i can write
%RUBY% %ARGS%
and it works.
but how can i do this in powershell?
It is important that the ruby installing path is variable.
I tried it with Start-Process but only ruby.exe was started, without script.
Any hints?
Thanks.
First, $args is an automatic variable managed by PowerShell, so I would avoid trying to declare your own variable with the same name.
To invoke a command stored in a variable from PowerShell, you can use the call operator &:
& $ruby 'script.rb'
If you need to build up the list of arguments, I would recommend creating an array, instead of mashing them all together into a single string:
$rubyArgs = #('C:\files\script.rb', $Arg1, $Arg2, $Arg3)
& $ruby $rubyArgs
If you need to pass more complicated arguments, you may find this answer useful.

How do I defer backticks (or $()) in a possibly-quoted variable in Bash?

I am trying to get Bash to execute the following minimized example properly:
# Runs a command, possibly quoted (i.e. single argument)
function run()
{
$*
}
run ls # works fine
run "ls" # also works
run "ls `pwd`" # also works, but pwd is eagerly evaluated (I want it to evaluate inside run)
run "ls \\\`pwd\\\`" # doesn't work (tried other variants as well)
To summarize, I am trying to get the ability of having commands in quoted strings (or not), and not having any of the command, including nested shell commands through backticks, calculated values, etc., evaluated before run() is called. Is this possible? How can I achieve this?
Well the way to do this sort of thing is to use the eval function associated with an escaped '$' :
function run()
{
eval $*
}
my_command="ls \$(pwd)"
Escaping '$' as '\$' ensure that my_command will be set to "ls $(pwd)" with no substitution. Then eval will provide the substitution ^^
then
run $my_command
cd ..
run $my_command
prove that you get your functionnality !
my2c

Resources