How do I execute a sed command from a Bash variable? - bash

I have a sed command that works when executed directly.
echo foo > /home/user/bar
sed -i 's/foo/zoo/' /home/user/bar
It also works when directly embedded in $(...) or `...`.
However, if I try to execute it from a Bash variable, I get an error:
CMD="sed -i 's/foo/zoo/' /home/user/bar"
$CMD
Error:
sed: -e expression #1, char 1: unknown command: `''
It also works if I echo it out and source the file:
echo $CMD > file
source file
What's going on here, and how do I get the sed command to run from a Bash variable?

Don't store full command a string variable to avoid word splitting. Use an array or shell function:
# store command in an array
cmd=(sed -i 's/foo/zoo/' /home/user/bar)
# execute the command
"${cmd[#]}"
Or else use a shell function:
fn() {
sed -i 's/foo/zoo/' /home/user/bar
}
#call it as:
fn
Read this BASH FAQ: I'm trying to put a command in a variable, but the complex cases always fail!

use eval : I am not sure why you want to do it. Note that using eval is a fragil solution and can cause your script to broke.
eval $CMD

Related

Executing the output as filename

In one of my Bash scripts, there's a point where I have a variable SCRIPT which contains the /path/to/an/exe, and what the script ultimately needs to do, is executing that executable. Therefore the last line of the script is
$($SCRIPT)
so that $SCRIPT is expanded to /path/to/an/exe, and $(/path/to/an/exe) executes the executable.
However, running shellcheck on the script generates this error:
In setscreens.sh line 7:
$($SCRIPT)
^--------^ SC2091: Remove surrounding $() to avoid executing output.
For more information:
https://www.shellcheck.net/wiki/SC2091 -- Remove surrounding $() to avoid e...
Is there a way I can rewrite that $($SCRIPT) in a more appropriate way? eval does not seem to be of much help here.
$($SCRIPT) indeed does not do what you think it does.
The outer $() will execute any commands inside the parenthesis and execute the result string.
The inner $SCRIPT will expand to the value of the SCRIPT variable and execute this string while splitting words on spaces/
If you want to execute the command contained into the SCRIPT variable, you just write as an example:
SCRIPT='/bin/ls'
"$SCRIPT" # Will execute /bin/ls
Now if you also need to handle arguments with your SCRIPT variable command call:
SCRIPT='/bin/ls'
"$SCRIPT" -l # Will execute /bin/ls -l
To also store or build arguments dynamically, you'd need an array instead of a string variable.
Example:
SCRIPT=(/bin/ls -l)
"${SCRIPT[#]}" # Will execute /bin/ls -l
SCRIPT+=(/etc) # Add /etc to the array
"${SCRIPT[#]}" # Will execute /bin/ls -l /etc
It worked for me with sh -c:
$ chrome="/opt/google/chrome/chrome"
$ sh -c "$chrome"
Opening in existing browser session.
It also passed the ShellCheck without any issues.
with bash, just use $SCRIPT:
cat <<'EOF' > test.sh
SCRIPT='echo aze rty'
$SCRIPT
EOF
bash test.sh
produce:
aze rty

bash run read a file and run the read line OR command

two commands with OR condition
test -e a.txt || test -e b.txt this command running without any problem from CLI but if I read from a file and try to run it gives sh: ||: unknown operand' error
cat test.txt
test -e a.txt || test -e b.txt
Read and Run the command
cat test.txt| while read command; do $command;done
sh: ||: unknown operand
Any thoughts
Very simplified, bash will:
Parse a command or structure, then for each command:
Apply brace expansion
Apply parameter expansion
Do word splitting
Apply pathname expansion
Execute the result
Handling of || happens during parsing in step 1, but you expand it in step 3. As a result, it's treated as a regular string as if running test -e a.txt "||" test -e b.txt.
It will similarly fail for commands like echo {1..10} which would require re-doing #2, and echo $PATH which would require re-doing #3.
Meanwhile, it will work for echo Hello (#4) and ls *.png (#4/#5) because these only use features that come after.
While having a command in a string is a code smell indicating that you're painting yourself into an awkward corner, you can use eval to apply all the steps over from #1 on a string of your choice:
cmd="test -e a.txt || test -e b.txt"
eval "$cmd"

Create alias from stdout

I am trying to dynamically create alias' from the output of another command line tool.
For example:
> MyScript
blender="/opt/apps/blender/blender/2.79/blender"
someOtherAlias="ls -l"
I am trying the following code:
MyScript | {
while IFS= read -r line;
do
`echo alias $line`;
done;
}
But when I run this, I get the following error:
bash: alias: -l": not found
Just trying to run this command by itself gives me the same error:
> `echo 'alias someOtherAlias="ls -l"'`
bash: alias: -l": not found
But obviously the following command does work:
alias someOtherAlias="ls -l"
I've tried to find someone else who may have done this before, but none of my searches have come up with anything.
I would appreciate any and all help. Thanks!
See how bash (and posix shells) command parsing and quoting works and see difference between syntax and literal argument: for example '.."..' "..'.." are litteral quotes in an argument whereas " or ' are shell syntax and are not part of argument
also, enabling tacing with set -x may help to understand :
set -x
`echo 'alias someOtherAlias="ls -l"'`
++ echo 'alias someOtherAlias="ls -l"'
+ alias 'someOtherAlias="ls' '-l"'
bash: alias: -l": not found
bash sees 3 words : alias, someOtherAlias="ls and -l".
and alias loops over its arguments if they contain a = it create an alias otherwise it displays what alias argument is as -l" is not an alias it shows the error.
Note also as backquotes means command is run in a subshell (can be seen with mutiple + in trace) it will have no effect in current shell.
eval may be use to reinterpret literal as bash syntax (or to parse again a string).
So following should work, but be careful using eval on arbitrary arguments (from user input) can run arbitrary command.
eval 'alias someOtherAlias="ls -l"'
Finally also as bash commands after pipe are also run in subshell.
while IFS= read -r line;
do
`echo alias $line`;
done <MyScript

How to search and replace in shell script for command line argument value

Im new bee to Shell script. Im trying to do search and replace for Command line argument value.
Ex: Sh script.sh 'ID,EmpName,Address'
In command line for $1 I have value like ID,EmpName,Address
Expected output : ID: chararray,EmpName: chararray,Address: chararray
Code tried
Sh script.sh 'ID,EmpName,Address'
Print 'sed -e 's/,/: chararray,/g' '"$2"''
You need:
echo "$2" | sed -e 's/,/: chararray,/'
or similar instead of:
sed -e 's/,/: chararray,/g' "$2"
As written sed thinks you are asking it to run on a file named ID,EmpName,Address, not on a string with that value.
Caveat: I have no idea what hadoop or apache-pig as tagged in your question are, the above is how to write the code in UNIX shell.

Why do I get a “Can't find string terminator” error only when the command is in a variable?

I wrote a simple shell script to get the version of Perl modules installed
on a server and I keep receiving the following error:
Can't find string terminator "'" anywhere before EOF at -e line 1.
Here is my script:
#!/bin/sh
#
mod_name="Sub::Uplevel"
tmp1="perl -M$mod_name -e 'print \"\$$mod_name::VERSION\"'"
echo $tmp1
$tmp1
If I just directly run the echo'd line (perl -MSub::Uplevel -e 'print "$Sub::Uplevel::VERSION"'), it works. Why doesn't the line work when its run from the variable $tmp1?
In place of just $tmp1, eval works:
eval "$tmp1"
That's because splitting a variable into words (for arguments) is done strictly by splitting on $IFS, not the normal input-parsing. eval forces the normal input parsing.
How did I figure this out?
Change your tmp1= line to put an echo in front, and you get:
perl -MSub::Uplevel -e 'print "$Sub::Uplevel::VERSION"'
Note that the ' are still there, which you wouldn't expect. If you write a quick script:
#!/bin/sh
for a in "$#"; do
echo "arg: $a"
done
and put a call to that in place of echo, you find how the arguments are really split:
arg: perl
arg: -MSub::Uplevel
arg: -e
arg: 'print
arg: "$Sub::Uplevel::VERSION"'
So, you can see that's splitting on spaces, so IFS.
It's always better to construct commands using bash arrays. That will keep arguments with whitespace properly grouped:
#!/bin/bash
mod_name="Sub::Uplevel"
perl_script=$(printf 'print "$%s::VERSION"' $mod_name)
tmp1=(perl -M$mod_name -e "$perl_script")
echo "${tmp1[#]}"
output=$( "${tmp1[#]}" )
Arrays are a bash feature, so the shebang line must reference bash not sh.
I'd usually write what you are doing with backticks, to run the command inside the shell:
#!/bin/sh
#
mod_name="Sub::Uplevel"
tmp1=`perl -M$mod_name -e 'print \"\$$mod_name::VERSION\"'`
echo $tmp1
Then you can work on $tmp1 as needed. It also avoids dealing with escaping.
Try to execute the script the below way(debugging the script):
sh -vx your_script.sh
Then you would be able to see where exactly the problem is.
I donot have the shell to execute it right now.

Resources