shell script variable in the middle of command - shell

#!/bin/bash
STR=000
while :
do
echo -e '\uE${STR}'
sleep 1
done
On a shell script, how can I merge a variable in a command? I would like the above to be the same as:
echo -e '\uE000'

Use double quotes ":
echo -e "\uE${STR}"
Single quotes ' prevent parameter expansion.

Related

In which order are these instructions executed?

I am using a shell script named script.sh that looks like that :
#!/bin/bash
STRING=$(cat my_string.txt)
${1}
In my_string.txt, there is only :
this_is_my_string
When I execute the commands :
$ STRING="not_my_string"
$ ./script.sh "echo $STRING"
The shell prints not_my_string instead of this_is_my_string and I don’t understand why.
Could you explain me ? And is there any way to force to print the value of the STRING variable which is defined inside the script ?
The variable $STRING is being expanded before the script is called, which is why not_my_string is being assigned.
To delay expansion until after the script is called you should replace "echo $STRING" with 'echo $STRING'. The single quotes cause the expansion to be delayed.
There is some discussion of delayed expansion here:
How to delay expansion of variable in bash if command should be executed on an other machine?
You will also need to replace ${1} in your script with eval ${1}, which will force the string to be executed and expanded.
$ STRING="not_my_string"
$ ./script.sh "echo $STRING"
During command execution bash will expand all variables to the values and actually the following command will be executed:
./script.sh "echo not_my_string"
You can use the following:
./script.sh 'echo $STRING' to send string as is and eval "${1} inside the script to execute argument

Bash script loop not incrementing

I have a script that inputs 2 -> file#.txt -> newline into my program. However, the script only inputs file4.txt into my program, even though the loop is running 10 times. I'm not sure why this is happening specifically for i=4.
gnome-terminal --working-directory=/path/to/dir/ -- bash -c "{ for i in {1..10};
do echo "2"; echo "file"$i".txt"; echo $'\n'; done; } | ./program; exec bash"
The $ characters are being processed by your original shell, not the shell executed inside gnome-terminal. Also, the embedded double quotes are delimiting the -c argument, not being passed explicitly to bash.
You need to escape the $ and " characters to preserve them.
gnome-terminal --working-directory=/path/to/dir/ -- bash -c "{ for i in {1..10};
do echo \"2\"; echo \"file\"\$i".txt\"; echo \$'\n'; done; } | ./program; exec bash"
You could also put the command in single quotes, but then you won't be able to embed $'\n' in it, because you can't escape quotes inside single quotes. But you can use printf instead of echo, since it will translate escape sequences itself.
gnome-terminal --working-directory=/path/to/dir/ -- bash -c '{ for i in {1..10};
do echo "2"; printf "file%d.txt\n\n" $i; done; } | ./program; exec bash'
See Difference between single and double quotes in Bash

Is there any difference in bash between using `eval $cmd` and just `$cmd`?

In bash, you can treat a string as command (and run it) in two different ways:
#!/bin/bash
cmd="echo -n sometext"
eval $cmd # Not sure if quotes make a difference here
and
#!/bin/bash
cmd="echo -n sometext"
$cmd # Not sure if quotes make a difference here either
Is there any difference between the two? Is there a situation where quotes around cmd make a difference? What about performance?
Yes, there is a difference :)
You need to first understand how eval works. Basically, eval is a shell builtin command. Whatever argument passed to eval is first treated as a string.
Let's take below example:
cmd="echo -n sometext"
eval $cmd
The complete run process of this command is as follows:
eval $cmd
+ eval echo -n sometext
++ echo -n sometext
sometext
Here, first $cmd first got evaluated and then the whole string was passed to eval command as argument. Then eval evaluates the command considering the first argument as a "command or an executable file" and then run as a normal command. So, here there is 2 rounds of evaluation getting performed for the execution of the complete command.
(NOTE: The + symbol above shows the step wise execution when used in bash -x mode)
The main consequence lies in variable expansion. With eval we have two rounds of expansion. One of course, when cmd is defined, and one when eval is executed.
var="inital"
cmd="echo -n $var \$var"
var="chanded in the mean time"
eval $cmd
inital chanded in the mean time
However, when you use $cmd only without eval, bash takes care of everything from variable expansion to the final execution. Just see the debugging window details while running only $cmd
$cmd
+ echo -n sometext
sometext
Performance wise, direct use of $cmd is good enough. However, when you are trying to use some external command or a script which requires environment changes, you can use eval
In cmd="echo -n sometext", quotes are necessary, otherwise after "echo", bash will raise an error like below:
cmd=echo -n sometext
-n: command not found
I hope the explanation will be helpful.

Bash: Echoing a echo command with a variable in bash

Ok, here is one I am struggling with as we speak. Echoing a echo command with a variable.
echo "creating new script file."
echo "#!/bin/bash" > $servsfile
echo "read -p "Please enter a service: " ser " >> $servfile
echo "servicetest=`getsebool -a | grep ${ser}` " >> $servfile
echo "if [ $servicetest > /dev/null ];then " >> $servfile
echo "echo "we are now going to work with ${ser}" " >> $servfile
echo "else" >> $servfile
echo "exit 1" >> $servfile
echo "fi" >> $servfile
My goal is create a script using echo commands then run it later. I just need to figure out how to echo echo/read commands while maintaining my variables.
edit: the variables need to transfer what's inside of them into the new file.
The immediate problem is you have is with quoting: by using double quotes ("..."), your variable references are instantly expanded, which is probably not what you want.
Use single quotes instead - strings inside single quotes are not expanded or interpreted in any way by the shell.
(If you want selective expansion inside a string - i.e., expand some variable references, but not others - do use double quotes, but prefix the $ of references you do not want expanded with \; e.g., \$var).
However, you're better off using a single here-doc[ument], which allows you to create multi-line stdin input on the spot, bracketed by two instances of a self-chosen delimiter, the opening one prefixed by <<, and the closing one on a line by itself - starting at the very first column; search for Here Documents in man bash or at http://www.gnu.org/software/bash/manual/html_node/Redirections.html.
If you quote the here-doc delimiter (EOF in the code below), variable references are also not expanded. As #chepner points out, you're free to choose the method of quoting in this case: enclose the delimiter in single quotes or double quotes, or even simply arbitrarily escape one character in the delimiter with \:
echo "creating new script file."
cat <<'EOF' > "$servfile"
#!/bin/bash
read -p "Please enter a service: " ser
servicetest=`getsebool -a | grep ${ser}`
if [ $servicetest > /dev/null ]; then
echo "we are now going to work with ${ser}"
else
exit 1
fi
EOF
As #BruceK notes, you can prefix your here-doc delimiter with - (applied to this example: <<-"EOF") in order to have leading tabs stripped, allowing for indentation that makes the actual content of the here-doc easier to discern.
Note, however, that this only works with actual tab characters, not leading spaces.
Employing this technique combined with the afterthoughts regarding the script's content below, we get (again, note that actual tab chars. must be used to lead each here-doc content line for them to get stripped):
cat <<-'EOF' > "$servfile"
#!/bin/bash
read -p "Please enter a service name: " ser
if [[ -n $(getsebool -a | grep "${ser}") ]]; then
echo "We are now going to work with ${ser}."
else
exit 1
fi
EOF
Finally, note that in bash even normal single- or double-quoted strings can span multiple lines, but you won't get the benefits of tab-stripping or line-block scoping, as everything inside the quotes becomes part of the string.
Thus, note how in the following #!/bin/bash has to follow the opening ' immediately in order to become the first line of output:
echo '#!/bin/bash
read -p "Please enter a service: " ser
servicetest=$(getsebool -a | grep "${ser}")
if [[ -n $servicetest ]]; then
echo "we are now going to work with ${ser}"
else
exit 1
fi' > "$servfile"
Afterthoughts regarding the contents of your script:
The syntax $(...) is preferred over `...` for command substitution nowadays.
You should double-quote ${ser} in the grep command, as the command will likely break if the value contains embedded spaces (alternatively, make sure that the valued read contains no spaces or other shell metacharacters).
Use [[ -n $servicetest ]] to test whether $servicetest is empty (or perform the command substitution directly inside the conditional) - [[ ... ]] - the preferred form in bash - protects you from breaking the conditional if the $servicetest happens to have embedded spaces; there's NEVER a need to suppress stdout output inside a conditional (whether [ ... ] or [[ ... ]], as no stdout output is passed through; thus, the > /dev/null is redundant (that said, with a command substitution inside a conditional, stderr output IS passed through).
You just need to use single quotes:
$ echo "$TEST"
test
$ echo '$TEST'
$TEST
Inside single quotes special characters are not special any more, they are just normal characters.
echo "echo "we are now going to work with ${ser}" " >> $servfile
Escape all " within quotes with \. Do this with variables like \$servicetest too:
echo "echo \"we are now going to work with \${ser}\" " >> $servfile
echo "read -p \"Please enter a service: \" ser " >> $servfile
echo "if [ \$servicetest > /dev/null ];then " >> $servfile

echo a one-liner bash script with variables

I would like to do that:
i="1"; echo -e '#!/usr/bin/env bash\nmyprogram -i "input_${i}.txt"'
and pipe it to a job scheduler.
However, this doesn't replace the variable i by its value. Instead, I obtain this:
#!/usr/bin/env bash
myprogram -i "input_${i}.txt"
I played a bit with option -e of echo and with single-/double-quote but could not make it work. For instance, I get this:
i="1"; echo -e "#!/usr/bin/env bash\nmyprogram -i \"input_${i}.txt\""
-bash: !/usr/bin/env: event not found
My bash version is 4.1.2.
Try this:
i="1"; echo -e '#!/usr/bin/env bash\nmyprogram -i '"\"input_${i}.txt\""
You can echo single- and double-quoted strings at the same time.
Try also escaping the exclamation mark:
\! should be okay, and will not be read as an "event" by bash.
i="1"; echo -e '#!/usr/bin/env bash\nmyprogram -i "input_'"${i}"'.txt"'
Basically, use single quotes until you need to interpolate, then close the single quotes, open the double quotes, add the interpolation, close the double quotes, reopen single quotes, and finish the string. In the shell, quotation marks don't delimit a word; they just change the interpretation of the part of a word falling between them.

Resources