Modify global variable from loop - linux

Why do I get
/tmp/test: line 4: 0=Done: command not found
from the below
a="0"
while [ true ]; do
$a="Done"
exit
done
echo $a
I were expecting it would output Done.

You don't need to use the $ when defining a variable, only when you are accessing it.
You'll need to change the line defining the variable a to:
a="Done"
As to an explanation, what I believe is happening here is that $a is being resolved to 0 and then the shell is seeing the entire 0=Done as a single (unfound) command. Accessing undefined variables still returns a 0 exit code.

Related

How can I write Shell script for default parameter setting

In shell script, How can I set 0 as default value to parameter.
param2=0
${2:-0}
and
${2:-param2}
These result is 0: not found
and
param=$5
S{1:-$param}
is active. but $0 mean shell script name. I want to use number 0.
echo "param: $param" # param is not defined
echo "param: ${param-0}" # use 0 as default
param=99 # set a value for param
echo "param: ${param-0}" # ignore the default
output:
param:
param: 0
param: 99
To understand problems like this, add set -x before the code. That will tell you that ${2:-0} is expanded to 0. The shell thinks this is a command and tries to execute it.
This will be easier to understand if you look at this code:
cmd=echo
${cmd} test
If you run this, ${cmd} is replaced with echo.
In your code, you probably want to assign the result of ${2:-0} to a new variable:
foo=${2:-0}

Dual use bash script - source but also exec subshell? Dynamic return/exit?

My current setup starts with a function that is ostensibly in .bashrc (.bash_it/custom/funcs.bash to be precise)
#!/usr/bin/env bash
function proset() {
. proset-core "$#";
}
proset-core does some decrypting of secrets and exports those secrets to the session, hence the need for the . instead of just running it as a script/subshell.
If something goes wrong in proset-core, I use return instead of exit since I don't want the SSH connection to be dropped.
if [ "${APP_JSON}" = "null" ] ; then
echo -e "\n${redtext}App named $NAME not found in ${APPCONF}. Aborting.${resettext}\n";
return;
fi
This makes sense in the context of the exported proset function, but precludes usage as a script since return isn't valid except from within a function.
Is there a way to detect how it's being called and return one or the other as appropriate?
Just try to return, and exit if it fails.
_retval=$?
return 2>/dev/null || exit "$_retval"
The only case where your code will still be continuing after the return was invoked at top-level (outside of a function) is if you were executed rather than sourced, and should that happen, exiting is the Right Thing.
Make the builtin variable $SHLVL part of $# args as the last arg. Then at test point:
if [ "${#: -1}" -lt $SHLVL ]; then
# SHLVL arg is less than current SHLVL
# we are in a subshell
exit
else
return
fi
Ended up using
calledBy="$(ps -o comm= $PPID)";
if [ "x${calledBy}" = "xsshd" ]; then
return 1;
else
exit 1;
fi
since it didn't require passing anything extra. Anything that might cause this to be problematic please comment. Not too worried about being bash-specific or portable.
Credit: get the name of the caller script in bash script

Bash variable assignment not working expected

status=0
$status=1
echo $status
Can anyone tell my what i am doing wrong with this?
It gives me the following error:
0=1: command not found
This line is OK - it assigns the value 0 to the variable status:
status=0
This is wrong:
$status=1
By putting a $ in front of the variable name you are dereferencing it, i.e. getting its value, which in this case is 0. In other words, bash is expanding what you wrote to:
0=1
Which makes no sense, hence the error.
If your intent is to reassign a new value 1 to the status variable, then just do it the same as the original assignment:
status=1
Bash assignments can't have a dollar in front. Variable replacements in bash are like macro expansions in C; they occur before any parsing. For example, this horrible thing works:
foof="[ -f"
if $foof .bashrc ] ; then echo "hey"; fi
Only use the $ when actually using the variable in bash. Omit it when assigning or re-assining.
e.g.
status=0
status2=1
status="$status2"
also this ugly thing works too :
status='a'
eval $status=1
echo $a
1

Exit code of variable assignment to command substitution in Bash

I am confused about what error code the command will return when executing a variable assignment plainly and with command substitution:
a=$(false); echo $?
It outputs 1, which let me think that variable assignment doesn't sweep or produce new error code upon the last one. But when I tried this:
false; a=""; echo $?
It outputs 0, obviously this is what a="" returns and it override 1 returned by false.
I want to know why this happens, is there any particularity in variable assignment that differs from other normal commands? Or just be cause a=$(false) is considered to be a single command and only command substitution part make sense?
-- UPDATE --
Thanks everyone, from the answers and comments I got the point "When you assign a variable using command substitution, the exit status is the status of the command." (by #Barmar), this explanation is excellently clear and easy to understand, but speak doesn't precise enough for programmers, I want to see the reference of this point from authorities such as TLDP or GNU man page, please help me find it out, thanks again!
Upon executing a command as $(command) allows the output of the command to replace itself.
When you say:
a=$(false) # false fails; the output of false is stored in the variable a
the output produced by the command false is stored in the variable a. Moreover, the exit code is the same as produced by the command. help false would tell:
false: false
Return an unsuccessful result.
Exit Status:
Always fails.
On the other hand, saying:
$ false # Exit code: 1
$ a="" # Exit code: 0
$ echo $? # Prints 0
causes the exit code for the assignment to a to be returned which is 0.
EDIT:
Quoting from the manual:
If one of the expansions contained a command substitution, the exit
status of the command is the exit status of the last command
substitution performed.
Quoting from BASHFAQ/002:
How can I store the return value and/or output of a command in a
variable?
...
output=$(command)
status=$?
The assignment to output has no effect on command's exit status, which
is still in $?.
Note that this isn't the case when combined with local, as in local variable="$(command)". That form will exit successfully even if command failed.
Take this Bash script for example:
#!/bin/bash
function funWithLocalAndAssignmentTogether() {
local output="$(echo "Doing some stuff.";exit 1)"
local exitCode=$?
echo "output: $output"
echo "exitCode: $exitCode"
}
function funWithLocalAndAssignmentSeparate() {
local output
output="$(echo "Doing some stuff.";exit 1)"
local exitCode=$?
echo "output: $output"
echo "exitCode: $exitCode"
}
funWithLocalAndAssignmentTogether
funWithLocalAndAssignmentSeparate
Here is the output of this:
nick.parry#nparry-laptop1:~$ ./tmp.sh
output: Doing some stuff.
exitCode: 0
output: Doing some stuff.
exitCode: 1
This is because local is actually a builtin command, and a command like local variable="$(command)" calls local after substituting the output of command. So you get the exit status from local.
I came across the same problem yesterday (Aug 29 2018).
In addition to local mentioned in Nick P.'s answer and #sevko's comment in the accepted answer, declare in global scope also has the same behavior.
Here's my Bash code:
#!/bin/bash
func1()
{
ls file_not_existed
local local_ret1=$?
echo "local_ret1=$local_ret1"
local local_var2=$(ls file_not_existed)
local local_ret2=$?
echo "local_ret2=$local_ret2"
local local_var3
local_var3=$(ls file_not_existed)
local local_ret3=$?
echo "local_ret3=$local_ret3"
}
func1
ls file_not_existed
global_ret1=$?
echo "global_ret1=$global_ret1"
declare global_var2=$(ls file_not_existed)
global_ret2=$?
echo "global_ret2=$global_ret2"
declare global_var3
global_var3=$(ls file_not_existed)
global_ret3=$?
echo "global_ret3=$global_ret3"
The output:
$ ./declare_local_command_substitution.sh 2>/dev/null
local_ret1=2
local_ret2=0
local_ret3=2
global_ret1=2
global_ret2=0
global_ret3=2
Note the values of local_ret2 and global_ret2 in the output above. The exit codes are overwritten by local and declare.
My Bash version:
$ echo $BASH_VERSION
4.4.19(1)-release
(not an answer to original question but too long for comment)
Note that export A=$(false); echo $? outputs 0! Apparently the rules quoted in devnull's answer no longer apply. To add a bit of context to that quote (emphasis mine):
3.7.1 Simple Command Expansion
...
If there is a command name left after expansion, execution proceeds as described below. Otherwise, the command exits. If one of the expansions contained a command substitution, the exit status of the command is the exit status of the last command substitution performed. If there were no command substitutions, the command exits with a status of zero.
3.7.2 Command Search and Execution [ — this is the "below" case]
IIUC the manual describes var=foo as special case of var=foo command... syntax (pretty confusing!). The "exit status of the last command substitution" rule only applies to the no-command case.
While it's tempting to think of export var=foo as a "modified assignment syntax", it isn't — export is a builtin command (that just happens to take assignment-like args).
=> If you want to export a var AND capture command substitution status, do it in 2 stages:
A=$(false)
# ... check $?
export A
This way also works in set -e mode — exits immediately if the command substitution return non-0.
As others have said, the exit code of the command substitution is the exit code of the substituted command, so
FOO=$(false)
echo $?
---
1
However, unexpectedly, adding export to the beginning of that produces a different result:
export FOO=$(false)
echo $?
---
0
This is because, while the substituted command false fails, the export command succeeds, and that is the exit code returned by the statement.

Checking cmd line argument in bash script bypass the source statement

I have an bash script "build.sh" like this:
# load Xilinx environment settings
source $XILINX/../settings32.sh
cp -r "../../../EDK/platform" "hw_platform"
if [ $# -ne 0 ]; then
cp $1/system.xml hw_platform/system.xml
fi
echo "Done"
Normally I run it as "./build.sh" and it execute the "source" statement to set environment variables correct. Sometimes I need to let the script to copy file from an alternative place, I run it as "./build.sh ~/alternative_path/"; My script check whether there is an cmd line argument by checking $# against 0.
When I do that, the "source" statement at the beginning of the script somehow get skipped, and build failed. I have put two "echo" before and after the "source", and I see echo statements get executed.
Currently I circumvent this issue by "source $XILINX/../settings32.sh; build.sh". However, please advise what I have done wrong in the script? Thanks.
Try storing the values of your positional paramaters first on an array variable then reset them to 0. "$XILINX/../settings32.sh" may be acting differently when it detects some arguments.
# Store arguments.
ARGS=("$#")
# Reset to 0 arguments.
set --
# load Xilinx environment settings
source "$XILINX/../settings32.sh"
cp -r "../../../EDK/platform" "hw_platform"
if [[ ${#ARGS[#]} -ne 0 ]]; then
cp "${ARGS[0]}/system.xml" hw_platform/system.xml
fi
echo "Done"

Resources