Bash Script; How to Use Vars and Funcs Defined After Command

Invoke function whose name is stored in a variable in bash

You should be able to just call the function directly using

$call_func

For everything else check out that answer: https://stackoverflow.com/a/17529221/3236102
It's not directly what you need, but it shows a lot of different ways of how to call commands / functions.

Letting the user execute any arbitrary code is bad practice though, since it can be quite dangerous. What would be better is to do it like this:

if [ $userinput == "some_command" ];then
some_command
fi

This way, the user can only execute the commands that you want them to and can even output an error message if the input was incorrect.

How do I store a command in a variable and use it in a pipeline?

The robust way to store a simple command in a variable in Bash is to use an array:

# Store the command names and arguments individually
# in the elements of an *array*.
cmd=( grep -P '^[^\s]*\s3\s' )

# Use the entire array as the command to execute - be sure to
# double-quote ${cmd[@]}.
echo 'before 3 after' | "${cmd[@]}"

If, by contrast, your command is more than a simple command and, for instance, involves pipes, multiple commands, loops, ..., defining a function is the right approach:

# Define a function encapsulating the command...
myGrep() { grep -P '^[^\s]*\s3\s'; }

# ... and use it:
echo 'before 3 after' | myGrep

Why what you tried didn't work:

var="grep -P '^[^\s]*\s3\s'"

causes the single quotes around the regex to become a literal, embedded part of $var's value.

When you then use $var - unquoted - as a command, the following happens:

  • Bash performs word-splitting, which means that it breaks the value of $var into words (separate tokens) by whitespace (the chars. defined in special variable $IFS, which contains a space, a tab, and a newline character by default).

    • Bash also performs globbing (pathname expansion) on the resulting works, which is not a problem here, but can have unintended consequences in general.
    • Also, if any of your original arguments had embedded whitespace, word splitting would split them into multiple words, and your original argument partitioning is lost.

      • (As an aside: "$var" - i.e., double-quoting the variable reference - is not a solution, because then the entire string is treated as the command name.)
  • Specifically, the resulting words are:

    • grep
    • -P
    • '^[^\s]*\s3\s' - including the surrounding single quotes
  • The words are then interpreted as the name of the command and its arguments, and invoked as such.

    • Given that the pattern argument passed to grep starts with a literal single quote, matching won't work as intended.

Short of using eval "$var" - which is NOT recommended for security reasons - you cannot persuade Bash to see the embedded single quotes as syntactical elements that should be removed (a process appropriate called quote removal).

Using an array bypasses all these problems by storing arguments in individual elements and letting Bash robustly assemble them into a command with "${cmd[@]}".

Using curl POST with variables defined in bash script functions

You don't need to pass the quotes enclosing the custom headers to curl. Also, your variables in the middle of the data argument should be quoted.

First, write a function that generates the post data of your script. This saves you from all sort of headaches concerning shell quoting and makes it easier to read an maintain the script than feeding the post data on curl's invocation line as in your attempt:

generate_post_data()
{
cat <<EOF
{
"account": {
"email": "$email",
"screenName": "$screenName",
"type": "$theType",
"passwordSettings": {
"password": "$password",
"passwordConfirm": "$password"
}
},
"firstName": "$firstName",
"lastName": "$lastName",
"middleName": "$middleName",
"locale": "$locale",
"registrationSiteId": "$registrationSiteId",
"receiveEmail": "$receiveEmail",
"dateOfBirth": "$dob",
"mobileNumber": "$mobileNumber",
"gender": "$gender",
"fuelActivationDate": "$fuelActivationDate",
"postalCode": "$postalCode",
"country": "$country",
"city": "$city",
"state": "$state",
"bio": "$bio",
"jpFirstNameKana": "$jpFirstNameKana",
"jpLastNameKana": "$jpLastNameKana",
"height": "$height",
"weight": "$weight",
"distanceUnit": "MILES",
"weightUnit": "POUNDS",
"heightUnit": "FT/INCHES"
}
EOF
}

It is then easy to use that function in the invocation of curl:

curl -i \
-H "Accept: application/json" \
-H "Content-Type:application/json" \
-X POST --data "$(generate_post_data)" "https://xxx:xxxxx@xxxx-www.xxxxx.com/xxxxx/xxxx/xxxx"

This said, here are a few clarifications about shell quoting rules:

The double quotes in the -H arguments (as in -H "foo bar") tell bash to keep what's inside as a single argument (even if it contains spaces).

The single quotes in the --data argument (as in --data 'foo bar') do the same, except they pass all text verbatim (including double quote characters and the dollar sign).

To insert a variable in the middle of a single quoted text, you have to end the single quote, then concatenate with the double quoted variable, and re-open the single quote to continue the text: 'foo bar'"$variable"'more foo'.

Bash lost variables after function

The shell runs whatever present under (..) in a sub-shell and especially variables the defined lose their scope once the shell terminates. You needed to enclose the function within {..} which encloses a compound statement in bash shell to make sure the commands within are run in the same shell as the one invoked.

function askExp { read -ep "$1" -n "$2" -r "$3"; }

As a small experiment you can observe the output of

hw()(
echo hello world from $BASHPID
)
hw
echo $BASHPID

and when running from the same shell.

hw(){
echo hello world from $BASHPID
}
hw
echo $BASHPID

The reason is in the former case, the variable set in the shell created inside (..) is lost in the local shell.

How to call a shell script function/variable from python?

No, that's not possible. You can execute a shell script, pass parameters on the command line, and it could print data out, which you could parse from Python.

But that's not really calling the function. That's still executing bash with options and getting a string back on stdio.

That might do what you want. But it's probably not the right way to do it. Bash can not do that many things that Python can not. Implement the function in Python instead.

How can I assign the output of a function to a variable using bash?

VAR=$(scan)

Exactly the same way as for programs.

Passing parameters to a Bash function

There are two typical ways of declaring a function. I prefer the second approach.

function function_name {
command...
}

or

function_name () {
command...
}

To call a function with arguments:

function_name "$arg1" "$arg2"

The function refers to passed arguments by their position (not by name), that is $1, $2, and so forth. $0 is the name of the script itself.

Example:

function_name () {
echo "Parameter #1 is $1"
}

Also, you need to call your function after it is declared.

#!/usr/bin/env sh

foo 1 # this will fail because foo has not been declared yet.

foo() {
echo "Parameter #1 is $1"
}

foo 2 # this will work.

Output:

./myScript.sh: line 2: foo: command not found
Parameter #1 is 2

Reference: Advanced Bash-Scripting Guide.

Return value in a Bash function

Although Bash has a return statement, the only thing you can specify with it is the function's own exit status (a value between 0 and 255, 0 meaning "success"). So return is not what you want.

You might want to convert your return statement to an echo statement - that way your function output could be captured using $() braces, which seems to be exactly what you want.

Here is an example:

function fun1(){
echo 34
}

function fun2(){
local res=$(fun1)
echo $res
}

Another way to get the return value (if you just want to return an integer 0-255) is $?.

function fun1(){
return 34
}

function fun2(){
fun1
local res=$?
echo $res
}

Also, note that you can use the return value to use Boolean logic - like fun1 || fun2 will only run fun2 if fun1 returns a non-0 value. The default return value is the exit value of the last statement executed within the function.

How do I set a variable to the output of a command in Bash?

In addition to backticks `command`, command substitution can be done with $(command) or "$(command)", which I find easier to read, and allows for nesting.

OUTPUT=$(ls -1)
echo "${OUTPUT}"

MULTILINE=$(ls \
-1)
echo "${MULTILINE}"

Quoting (") does matter to preserve multi-line variable values; it is optional on the right-hand side of an assignment, as word splitting is not performed, so OUTPUT=$(ls -1) would work fine.

Returning value from called function in a shell script

A Bash function can't return a string directly like you want it to. You can do three things:

  1. Echo a string
  2. Return an exit status, which is a number, not a string
  3. Share a variable

This is also true for some other shells.

Here's how to do each of those options:

1. Echo strings

lockdir="somedir"
testlock(){
retval=""
if mkdir "$lockdir"
then # Directory did not exist, but it was created successfully
echo >&2 "successfully acquired lock: $lockdir"
retval="true"
else
echo >&2 "cannot acquire lock, giving up on $lockdir"
retval="false"
fi
echo "$retval"
}

retval=$( testlock )
if [ "$retval" == "true" ]
then
echo "directory not created"
else
echo "directory already created"
fi

2. Return exit status

lockdir="somedir"
testlock(){
if mkdir "$lockdir"
then # Directory did not exist, but was created successfully
echo >&2 "successfully acquired lock: $lockdir"
retval=0
else
echo >&2 "cannot acquire lock, giving up on $lockdir"
retval=1
fi
return "$retval"
}

testlock
retval=$?
if [ "$retval" == 0 ]
then
echo "directory not created"
else
echo "directory already created"
fi

3. Share variable

lockdir="somedir"
retval=-1
testlock(){
if mkdir "$lockdir"
then # Directory did not exist, but it was created successfully
echo >&2 "successfully acquired lock: $lockdir"
retval=0
else
echo >&2 "cannot acquire lock, giving up on $lockdir"
retval=1
fi
}

testlock
if [ "$retval" == 0 ]
then
echo "directory not created"
else
echo "directory already created"
fi


Related Topics



Leave a reply



Submit