Define bash variable to be evaluated every time it is used - linux

I want to define bash a variable which will be evaluated every time it is used.
My goal is to define two variables:
A=/home/userA
B=$A/my_file
So whenever I update A, B will be updated with the new value of A
I know how to do it in prompt variables, but, is there a way to do it for regular variables?

If you have Bash 4.4 or newer, you could (ab)use the ${parameter#P} parameter expansion, which expands parameter as if it were a prompt string:
$ A='/home/userA'
$ B='$A/my_file' # Single quotes to suppress expansion
$ echo "${B#P}"
/home/userA/my_file
$ A='/other/path'
$ echo "${B#P}"
/other/path/my_file
However, as pointed out in the comments, it's much simpler and more portable to use a function instead:
$ appendfile() { printf '%s/%s\n' "$1" 'my_file'; }
$ A='/home/user'
$ B=$(appendfile "$A")
$ echo "$B"
/home/user/my_file
$ A='/other/path'
$ B=$(appendfile "$A")
$ echo "$B"
/other/path/my_file

No. Use a simple and robust function instead:
b() {
echo "$a/my_file"
}
a="/home/userA"
echo "b outputs $(b)"
a="/foo/bar"
echo "b outputs $(b)"
Result:
b outputs /home/userA/my_file
b outputs /foo/bar/my_file
That said, here's one ugly way of fighting the system accomplish your goal verbatim:
# Trigger a re-assignment after every single command
trap 'b="$a/my_file"' DEBUG
a="/home/userA"
echo "b is $b"
a="/foo/bar"
echo "b is $b"
Result:
b is /home/userA/my_file
b is /foo/bar/my_file

Related

How to write a bash script to label each argument like this:

$ bash argcnt.sh this is a "real live" test
is
real live
(to display only paired arguments)
Because, I know only in this way:
#!/bin/bash
echo "$2"
echo "$4"
It seems like you want to print every other argument given to the script. You could then create a loop over $#:
#!/bin/bash
# idx will be 2, 4, 6 ... for as long as it's less than the number of arguments given
for ((idx = 2; idx < ${##}; idx += 2))
do
# variable indirection below:
echo "${!idx}"
done
Note: You can use $# instead of ${##} to get the number of elements in $# too. I don't know which one that is preferred by people in general.
If what you want is print every other argument, starting from the second, you can use shift:
$ cat argcnt
#!/bin/bash
while shift; do printf '%s\n' "$1"; shift; done
$ ./argcnt this is a "real live" test foo
is
real live
foo

Bash call variables in for loop variables

I am curious to know that whether it is possible in bash that we can run for loop on a bunch of variables and call those values within for loop. Example:
a="hello"
b="world"
c="this is bash"
for f in a b c; do {
echo $( $f )
OR
echo $ ( "$f" )
} done
I know this is not working but can we call the values saved in a, b and c variables in for loop with printing f. I tried multiple way but unable to resolve.
You need the ! like this:
for f in a b c; do
echo "${!f}"
done
You can also use a nameref:
#!/usr/bin/env bash
a="hello"
b="world"
c="this is bash"
declare -n f
for f in a b c; do
printf "%s\n" "$f"
done
From the documentation:
If the control variable in a for loop has the nameref attribute, the list of words can be a list of shell variables, and a name reference will be established for each word in the list, in turn, when the loop is executed.
Notes on the OP's code, (scroll to bottom for corrected version):
for f in a b c; do {
echo $( $f )
} done
Problems:
The purpose of { & } is usually to put the separate outputs of
separate unpiped commands into one stream. Example of separate
commands:
echo foo; echo bar | tac
Output:
foo
bar
The tac command puts lines of input in reverse order, but in the
code above it only gets one line, so there's nothing to reverse.
But with curly braces:
{ echo foo; echo bar; } | tac
Output:
bar
foo
A do ... done already acts just like curly braces.
So "do {" instead of a "do" is unnecessary and redundant; but it
won't harm anything, or have any effect.
If f=hello and we write:
echo $f
The output will be:
hello
But the code $( $f ) runs a subshell on $f which only works if $f is
a command. So:
echo $( $f )
...tries to run the command hello, but there probably is no such
command, so the subshell will output to standard error:
hello: command not found
...but no data is sent to standard output, so echo will
print nothing.
To fix:
a="hello"
b="world"
c="this is bash"
for f in "$a" "$b" "$c"; do
echo "$f"
done

Bash, is subshell output implicitly quoted

In the following example
for f in *
do
res=$(ls -la "$f")
echo "$res"
done
Is this the correct way to use quotes? I know I should always quote the variable, but is the subshell output implicitly quoted?
The output from command substitution ($()) is not implicitly quoted:
$ for i in $(echo "foo bar"); do echo $i; done
foo
bar
The loop above splits the unquoted output along words. We can prevent this behavior by quoting the result:
$ for i in "$(echo "foo bar")"; do echo $i; done
foo bar
However, when assigning a variable, as in your example, the result of the subshell is not split, even without quotes:
$ baz=$(echo "foo bar")
$ echo "$baz"
foo bar
Unlike StackOverflow's syntax highlighting, the shell understands quotes inside command substitution, so we don't need to escape nested quotes:
$ baz="foo"
$ echo "$(echo "$baz $(echo "bar")")"
foo bar

When does a variable add $ in bash

I'm recently learning bash and confused when a variable would add $. I find code like:
i=1
while [ $i -le 10 ]
do
echo "$n * $i = `expr $i \* $n`"
i=`expr $i + 1`
done
The $ substitutes the variable. Writing $i will insert the value of i, no matter where you write it.
If you want to assign to the variable, that obviously makes no sense.
I thought #slaks' [ answer ] wouldn't be complete without this :
When not to add $ for a variable
With The Double-Parentheses [ Construct ]
x=5;
(( x++ )) # fine, note this construct accept $x form too.
When using export
var=stuff
export var #fine
When using declare
declare -a arry # fine
When not omit $
As #rici pointed out in the comment below:
you can leave out the $ in any arithmetic context, not just ((...))
and $((...)) ... For example, if arr is an array (not associative), then
${arr[x++]} is also fine.
Consider
# You wanted to create an associative array 'test' but you forgot to do
# declare -A test , Now below
test[foo]=bar # is foo a variable or a key, the reader isn't clear
# creates a simple array
echo ${test[foo]} # is foo a variable or a key?
bar
declare -p test
declare -a test='([0]="bar")'
# What happened?
# Since foo was not set at the point when 'test[foo]=bar' was called,
# bash substituted it with zero
# I meant to say test[foo]=bar hides an error.
A key thing to remember is that variables are never passed around in shell, only values. When you call something like
echo "$foo"
you might think that echo receives $foo, then looks at its value. Instead, the shell first expands $foo to the value of foo, then passes that value to echo.
The dollar sign is used to introduce any such parameter expansion, where the value of a parameter is needed. Consider:
$ foo=10
$ echo foo
foo
$ echo $foo
10
From the perspective of the echo command, there is no difference between echo $foo and echo 10; in both cases, the value passed to echo is 10.
To set a value to a variable in bash you can do
a=10
Where as to access the value of that variable you need to use
echo $a which mean $ is a symbol used to access the value of a variable.
i=1 ---> setting variable as 1
while [ $i -le 10 ] ---> simple while statement which loops till value of i is less that 10
do
echo "$n * $i = `expr $i \* $n`" ---> This is a syntax error bcoz value of n is never assigned
i=`expr $i + 1` ---> This line add's one to value of i
done ---> terminate while loop
Hope that explains you.

In Bash, why `x=100 echo $x` doesn't print anything? [duplicate]

This question already has answers here:
Why can't I specify an environment variable and echo it in the same command line?
(9 answers)
Closed 2 years ago.
I saw codes like this:
fqdn='computer1.daveeddy.com'
IFS=. read hostname domain tld <<< "$fqdn"
echo "$hostname is in $domain.$tld"
# => "computer1 is in daveeddy.com"
I think it works because IFS is assigned to . in the third line.. So I tried this:
x=100 echo $x
but found the bash doesn't print anything, while I expect it will print 100..
Moreover, I found x=100 echo $x; echo $x print nothing, while x=100; echo $x prints 100, which is very confusing.
Does anyone have ideas about this?
The $x is expanded before echo runs, and the result is passed to echo as an argument. echo does not use the value of x in its environment.
In the first example, read uses the value of IFS in its environment to split the string it receives via the here string.
here is another way to think about it:
$ a="echo 100" $a
This is equal to:
$ a="echo 100"
Because at the time of scanning the line, $a is empty. Variable substition occurs first, so the $a just disappears.
Compare this to a very similar statment:
$ a="echo 100"; $a # returns "100"

Resources