Default values for failed backticks in bash in one command - bash

I am trying to come up with a mechanism to assign a default value to a variable when a command fails. I can't seem to combine them with || or inserting $() inside the 2nd line.
How do I convert this to one command?
MY_COMMAND=$(which somefile)
MY_COMMAND=${MY_COMMAND:-"/usr/local/dev/branch/somefile"}

The following should work in Bash:
MY_VARIABLE="$(which thing || echo 'default')"
As this is using a subshell ($() syntax), there should be a complete command on each side of the || or symbol. So the second part should echo something.
On the opposite, ${} is used in parameter substitution, and the variable names can't be nested.

Related

Store string values in a variable using sh

I need to store this tags values in a variable in a .sh file.
TAGS = "sample=test" \
"appenv=dev"
But it's throwing parsing error while printing using echo $TAGS.
How can I do this correctly?
To assign a variable in Bash or sh, you need to use VARNAME=val with no spaces around the =.
If you type something like
IFS='|' read -r a b
the shell will interpret that as
Use the value | for the variable IFS—but only for the command on this line. (The value of IFS variable for subsequent commands will be whatever is already stored there.)
Execute the command read passing it the arguments -r, a, and b.
The shell also uses quotation marks (") and equal signs (=) for its own purposes. If you need those in the variable, you can use single quotes so the shell treats everything inside them as regular characters.
$ TAGS='"sample=test"
> "appenv=dev"'
Now my TAGS variable has the value "sample=test"\n"appenv=dev". That \n is the newline character I typed after …test". Because I had an open single quote ', Bash knew I wasn't done with the command, so it put the \n in the value and prompted me with > to continue.

Unexpected behavior of the unix echo command

while writing some script i found the following issue ,
suppose
set x = "param[xyz]";
echo $x
the output was echo:No match
But when i do ,
echo "$x"
i got the output: param[xyz]
so echo is doing a two way substitution ,
Initially echo $x was converted to echo param[xyz] and then it tried to look for the param[xyz] value .
But Ideally it should have just printed the variable whatever provided to it .
Does this behavior is a valid use case?
echo does no substitution at all, it's the shell that does it. It depends on the shell you use, but it seems that you are using a shell of the family of c-shells. Shell expands variables in the command line, so the first step is to generate:
[csh] echo param[xyz]
and then the shell does file match expansion, but as there is no file that correspond to the pattern the shell answers that there is no match. The message is somehow misleading as the shell reminds you what "command" was concerned not that the command failed by itself.
In the second try, enclosing the variable inside " prevent the shell to do other expansion and the shell launch the command with the argument obtained after the first expansion.
There exists another prevention if you use ', the shell won't ever expand variables:
[csh] echo '$x'
$x
Please refer to shell documentation and especially about expansion.
Another experience to convince you is to try with an non existing command:
[csh] weirdo z*
weirdo: No match
which is different than an non existing command:
[csh] weirdo
weirdo: Command not found.
If you'd use other shell behavior would be different:
[bash] echo z*
z*
because that shell produces as the argument the string itself in the case file matching is not working.
With:
[zsh] echo z*
zsh: no matches found: z*
the behavior is much more similar to c-shells but the message is much more clear, the shell failed at matching.

Echo-ing an environment variable returns string literal rather than environment variable value

I have two bash scripts. The first listens to a pipe "myfifo" for input and executes the input as a command:
fifo_name="myfifo"
[ -p $fifo_name ] || mkfifo $fifo_name;
while true
do
if read line; then
$line
fi
done <"$fifo_name"
The second passes a command 'echo $SET_VAR' to the "myfifo" pipe:
command='echo $SET_VAR'
command_to_pass="echo $command"
$command_to_pass > myfifo
As you can see, I want to pass 'echo $SET_VAR' through the pipe. In the listener process, I've set a $SET_VAR environment variable. I expect the output of the command 'echo $SET_VAR' to be 'var_value,' which is the value of the environment variable SET_VAR.
Running the first (the listener) script in one bash process and then passing a command via the second in another process gives the following result:
$SET_VAR
I expected to "var_value" to be printed. Instead, the string literal $SET_VAR is printed. Why is this the case?
Before I get to the problem you're reporting, I have to point out that your loop won't work. The while true part (without a break somewhere in the loop) will run forever. It'll read the first line from the file, loop, try to read a second line (which fails), loop again, try to read a third line (also fails), loop again, try to read a fourth line, etc... You want the loop to exit as soon as the read command fails, so use this:
while read line
do
# something I'll get to
done <"$fifo_name"
The other problem you're having is that the shell expands variables (i.e. replaces $var with the value of the variable var) partway through the process of parsing a command line, and when it's done that it doesn't go back and re-do the earlier parsing steps. In particular, if the variable's value included something like $SET_VAR it doesn't go back and expand that, since it's just finished the bit where it expands variables. In fact, the only thing it does with the expanded value is split it into "words" (based on whitespace), and expand any filename wildcards it finds -- no variable expansions happen, no quote or escape interpretation, etc.
One possible solution is to tell the shell to run the parsing process twice, with the eval command:
while read line
do
eval "$line"
done <"$fifo_name"
(Note that I used double-quotes around "$line" -- this prevents the word splitting and wildcard expansion I mentioned from happening before eval goes through the normal parsing process. If you think of your original code half-parsing the command in $line, without double-quotes it gets one and a half-parsed, which is weird. Double-quotes suppress that half-parsing stage, so the contents of the variable get parsed exactly once.)
However, this solution comes with a big warning, because eval has a well-deserved reputation as a bug magnet. eval makes it easy to do complex things without quite understanding what's going on, which means you tend to get scripts that work great in testing, then fail incomprehensibly later. And in my experience, when eval looks like the best solution, it probably means you're trying to solve the wrong problem.
So, what're you actually trying to do? If you're just trying to execute the lines coming from the fifo as shell commands, then you can use bash "$fifo_name" to run them in a subshell, or source "$fifo_name" to run them in the current shell.
BTW, the script that feeds the fifo:
command='echo $SET_VAR'
command_to_pass="echo $command"
$command_to_pass > myfifo
Is also a disaster waiting to happen. Putting commands in variables doesn't work very well in the shell (I second chepner's recommendation of BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!), and putting a command to print another command in a variable is just begging for trouble.
bash, by it's nature, reads commands from stdin. You can simply run:
bash < myfifo

Line feed is being removed from echo when called in double-quotes

I'm trying to populate a shell variable called $recipient which should contain a value followed by a new-line.
$ set -x # force bash to show commands as it executes them
I start by populating $user, which is the value that I want to be followed by the newline.
$ user=user#xxx.com
+ user=user#xxx.com
I then call echo $user inside a double-quoted command substitution. The echo statement should create a newline after $user, and the double-quotes should preserve the newline.
$ recipient="$(echo $user)"
++ echo user#xxx.com
+ recipient=user#xxx.com
However when I print $recipient, I can see that the newline has been discarded.
$ echo "'recipient'"
+ echo ''\''recipient'\'''
'recipient'
I've found the same behaviour under bash versions 4.1.5 and 3.1.17, and also replicated the issue under dash.
I tried using "printf" rather than echo; this didn't change anything.
Is this expected behaviour?
Command substitution removes trailing newlines. From the standard:
The shell shall expand the command substitution by executing command in a subshell environment (see Shell Execution Environment ) and replacing the command substitution (the text of command plus the enclosing "$()" or backquotes) with the standard output of the command, removing sequences of one or more characters at the end of the substitution. Embedded characters before the end of the output shall not be removed; however, they may be treated as field delimiters and eliminated during field splitting, depending on the value of IFS and quoting that is in effect. If the output contains any null bytes, the behavior is unspecified.
You will have to explicitly add a newline. Perhaps:
recipient="$user
"
There's really no reason to use a command substitution here. (Which is to say that $(echo ...) is almost always a silly thing to do.)
All shell versions will react the same way, this is nothing new in scripting.
The new-line at the end of your original assignment is not included in the variable's value. It only "terminates" the current cmd and signals the shell to process.
Maybe user="user#xxx.com\n" will work, but without context about why you want this, just know that people usually keep variables values separate from the formatting "tools" like the newline.
IHTH.

bash shell script conditional assignment

I'm writing a bash shell script. There's a required first argument and I want to have an optional second argument.
If the second argument is omitted I want it to use the value of the first argument.
Currently I have:
SOMEVAR=${2:-Untitled}
How can I use something like basename $1 instead of Untitled?
You can just do something like SOMEVAR=${2:-$(basename "$1")}. You can do any shell or variable in the optional part.
Just use command substitution: $(basename $1), literally instead of Untitled.
However, bash also has the ability to do this without an external process: ${1##*/}
SOMEVAR=${2:-${1##*/}}

Resources