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
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
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"
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
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.
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.