In BASH, can we assign and display the value in the variable _ (underscore)? - linux

Answering to the following question:
Allowed characters in linux environment variable names #aiden-bell writes that the following patterns gives all allowed shell variable names in BASH : [a-zA-Z_]+[a-zA-Z0-9_]*
I found this to be true. In fact I can export value _="Just for fun". Unfortunately though, whenever I print it I get __bp_preexec_invoke_exec
I went through this thread and while it is instructive it doesn't actually answer my question. Irrespective of whatever the shell might do with the variable $_, can I use it for my own means? Also, whatever exactly is __bp_preexec_invoke_exec? Thanks and regards.

You can assign to the special parameter _, but the shell will also update its value after each command. Typically, you only use it as a dummy variable where you don't care about the result, such as in something like
while read _ second _ ; do ...; done < input.txt
where you only care about the second column of each input line.
From the man page:
_ At shell startup, set to the absolute pathname used to invoke
the shell or shell script being executed as passed in the envi-
ronment or argument list. Subsequently, expands to the last
argument to the previous command, after expansion. Also set to
the full pathname used to invoke each command executed and
placed in the environment exported to that command. When check-
ing mail, this parameter holds the name of the mail file cur-
rently being checked.

Related

How to modify command line arguments inside bash script using set

I'm executing a shell script and passing few command line arguments to it.
I want to modify the arguments inside the script using set. Not all at once depending upon some conditions.
How can I do that?
Copy unmodified arguments at their respective location within set --
Say you want to modify value of argument 2:
set -- "${#::2}" 'new arg2 value' "${#:3}"
Explanation:
"${#::2}": Expands 2 arguments from index 0 (arguments 0 and 1)
new arg2 value: Becomes the value for argument 2.
"${#:3}": Expands all argument values starting at index 3.
Opinion:
Anyway, having mutable arguments is considered code-smell in modern programming. So I'd recommend you reconsider your approach to the problem you are trying to solve.

In Bash, is there a way to expand variables twice in double quotes?

For debugging my scripts, I would like to add the internal variables $FUNCNAME and $LINENO at the beginning of each of my outputs, so I know what function and line number the output occurs on.
foo(){
local bar="something"
echo "$FUNCNAME $LINENO: I just set bar to $bar"
}
But since there will be many debugging outputs, it would be cleaner if I could do something like the following:
foo(){
local trace='$FUNCNAME $LINENO'
local bar="something"
echo "$trace: I just set bar to $bar"
}
But the above literally outputs:
"$FUNCNAME $LINENO: I just set bar to something"
I think it does this because double quotes only expands variables inside once.
Is there a syntactically clean way to expand variables twice in the same line?
You cannot safely evaluate expansions twice when handling runtime data.
There are means to do re-evaluation, but they require trusting your data -- in the NSA system design sense of the word: "A trusted component is one that can break your system when it fails".
See BashFAQ #48 for a detailed discussion. Keep in mind that if you could be logging filenames, that any character except NUL can be present in a UNIX filename. $(rm -rf ~)'$(rm -rf ~)'.txt is a legal name. * is a legal name.
Consider a different approach:
#!/usr/bin/env bash
trace() { echo "${FUNCNAME[1]}:${BASH_LINENO[0]}: $*" >&2; }
foo() {
bar=baz
trace "I just set bar to $bar"
}
foo
...which, when run with bash 4.4.19(1)-release, emits:
foo:7: I just set bar to baz
Note the use of ${BASH_LINENO[0]} and ${FUNCNAME[1]}; this is because BASH_LINENO is defined as follows:
An array variable whose members are the line numbers in source files where each corresponding member of FUNCNAME was invoked.
Thus, FUNCNAME[0] is trace, whereas FUNCNAME[1] is foo; whereas BASH_LINENO[0] is the line from which trace was called -- a line which is inside the function foo.
Although eval has its dangers, getting a second expansion is what it does:
foo(){
local trace='$FUNCNAME $LINENO'
local bar="something"
eval echo "$trace: I just set bar to $bar"
}
foo
Gives:
foo 6: I just set bar to something
Just be careful not to eval anything that has come from external sources, since you could get a command injected into the string.
Yes to double expansion; but no, it won't do what you are hoping for.
Yes, bash offers a way to do "double expansion" of a variable, aka, a way to first interpret a variable, then take that as the name of some other variable, where the other variable is what's to actually be expanded. This is called "indirection". With "indirection", bash allows a shell variable to reference another shell variable, with the final value coming from the referenced variable. So, a bash variable can be passed by reference.
The syntax is just the normal braces style expansion, but with an exclamation mark prepended to the name.
${!VARNAME}
It is used like this:
BAR="my final value";
FOO=BAR
echo ${!FOO};
...which produces this output...
my final value
No, you can't use this mechanism to do the same as $( eval "echo $VAR1 $VAR2" ). The result of the first interpretation must be exactly the name of a shell variable. It does not accept a string, and does not understand the dollar sign. So this won't work:
BAR="my final value";
FOO='$BAR'; # The dollar sign confuses things
echo ${!FOO}; # Fails because there is no variable named '$BAR'
So, it does not solve your ultimate quest. None-the-less, indirection can be a powerful tool.

Adding multiple user inputs into one variable in Bash

I am fairly new to unix bash scripting and need to know if this is possible. I want to ask user for their input multiple times and then store that input in to one variable.
userinputs= #nothing at the start
read string
<code to add $string to $userinputs>
read string
<code to add $string to $userinputs> #this should add this input along with the other input
so if the user enters "abc" when asked first time, it add's "abc" in $userinputs
then when asked again for the input and the user enters "123" the script should store it in the same $userinputs
this would make the $userinput=abc123
The usual way to concat two strings in Bash is:
new_string="$string1$string2"
{} are needed around the variable name only if we have a literal string that can obstruct the variable expansion:
new_string="${string1}literal$string2"
rather than
new_string="$string1literal$string2"
You can also use the += operator:
userinputs=
read string
userinputs+="$string"
read string
userinputs+="$string"
Double quoting $string is optional in this case.
See also:
How to concatenate string variables in Bash?
You can concatentate variables and store multiple strings in the same one like so:
foo=abc
echo $foo # prints 'abc'
bar=123
foo="${foo}${bar}"
echo $foo # prints 'abc123'
You can use the other variables, or the same variable, when assigning to a variable, e.g. a="${a}123${b}". See this question for more info.
You don't have to quote the strings you're assigning to, or do the ${var} syntax, but learning when to quote and not to quote is a surprisingly nuanced art, so it's often better to be safe than sorry, and the "${var}" syntax in double quotes is usually the safest approach (see any of these links for more than you ever wanted to know: 1 2 3).
Anyway, you should read into a temporary variable (read, by default, reads into $REPLY) and concatentate that onto your main variable, like so:
allinput=
read # captures user input into $REPLY
allinput="${REPLY}"
read
allinput="${allinput}${REPLY}"
Beware that the read command behaves very differently depending on supplied switches and the value of the IFS global variable, especially in the face of unusual input with special characters. A common "just do what I mean" choice is to empty out IFS via IFS= and use read -r to capture input. See the read builtin documentation for more info.

what does this notation in hive script(hivequery.hql) file mean "use ${word:word}"

The script (hivequery.hql:) looks like this:
Use ${platformType:platformName};
select * from hivetablename;
And this script is being called in a bash script as
#!/usr/bin/env bash
hive -f hivequery.hql
Within an hql file, the use command sets the default database. See Use Database.
The ${platformType:platformName} is Hive's variable notation where platformType is the namespace and platformName is the variable name. This is explained in the Using Variables section of the Language Manual.
If you want to see what value a specific variable has, you can just use set like:
set platformType:platformName;
and it will print out the value. You can also run set; to get a full listing of known variables in all namespaces.
The more correct way to write the construct ${word:word} would be to write ${parameter:offset} . It cause parameter expansion, it expands to the portion of the value of parameter starting at the character (counting from 0 ) determined by expanding offset to the end of the parameter . It has one more variant as ${parameter:offset:length } - Expands to the portion of the value of parameter starting at the character (counting from 0 ) determined by expanding offset as an arithmetic expression and consisting of the number of characters determined by the arithmetic expression defined by length.
So I think basically the in your case , it is meant to get the name of the database from platformType.
For more details on this look into the
Look for Parameter Expansion in the bash man page.

Passing List of Variables In Bash To External Program

Good Afternoon Everyone,
This is probably a no-brainer but, I'm currently having issues passing a variable to a program in my bash script.
Here's what I'm trying to do:
regions=ne,se,vt,ma,sw,nw and so on and so forth
After that variable has been defined in my bash script, I'd then like to pass that variable into GrADS, where my script will read each of the regions one after the other until it reaches the end.
The most reliable means of passing variables I've found is to generate a text file with the code (or just the string) you want to pass from within the code. Alternatively, you could call GrADS (?) from within whatever program is generating the variable, and pass "$regions" as an argument.

Resources