bash: problem running command with quotes - bash

In answering another question I created the following script bash script:
#!/bin/bash
files1=( file1.txt file2.txt file3.txt )
files2=( file1_.txt file2_.txt file3_.txt )
cmd="vim -c 'set diffopt=filler,vertical' -c 'edit ${files1[0]}' -c 'diffsplit ${files2[0]}' "
echo $cmd
for i in {1..2}; do
cmd="${cmd} -c 'tabe ${files1[i]}' -c 'diffsplit ${files2[i]}' "
done
#$cmd
echo $cmd
the problem is that if I try to run
$cmd
in the end of the script I get errors, but if I just use echo $cmd and then copy and paste in the command line it works just fine.
Any ideas what I am doing wrong?
Thanks.

Use:
eval $cmd
So that the variables within the expression are expanded before execution.

BASH FAQ entry #50: "I'm trying to put a command in a variable, but the complex cases always fail!"

Related

Argument evaluation

Look at this bash script (script.sh):
#!/bin/bash
echo "aaa"
echo "bbb"
...
echo $1
...
Now, i am trying to run this script this way:
./script.sh $(cat file1)
I have a problem:
the "cat file1" is run before script.sh. Bash is evaluating all arguments before running script.sh
I would like to run "cat file1" inside script.sh, on the "echo $1" line.
How can i do this ?
I have tried this:
./script.sh $(eval 'cat file1')
But it gives me the same result...
Thanks
I think, there is no way unless you can modify the script script.sh.
If you can modify script.sh, you can write your script like this:
#!/bin/bash
echo "$($1)"
Then call it in this way:
./script.sh "cat file1"
This means, you pass the command to be executed to the script and you echo the result of the executed command in the script.
But the above script is a little cumbersome. This one is simpler and would do the same:
#!/bin/bash
$1

Execute script to inside another bash script

On my server I try run:
#!/bin/bash
PATH="/SANCFS/stats/scripts/"
for (( i=6;i<=8;i++ ));
do
echo "Running $i"
exec "/SANCFS/stats/scripts/load_cdrs.sh --debug --config /SANCFS/stats/scripts/iquall-mm4-cdr.cfg --date '2018-10-0"$i"' >> /home/stats/201810/load_cdrsIMRMM4-0"$i".ok 2>>/home/stats/201810/load_cdrsIMRMM4-0"$i".err"
done
And the result is:
cannot execute: No such file or directory
Your help, how edit/modify to run successfully ?
Here's an easier way to reproduce your problem:
$ exec "echo "hello world""
bash: exec: echo hello: not found
Running a command in bash does not require adding exec or quotes:
$ echo "hello world"
hello world
Additionally, you are using $i in single quotes in one case, and you're overwriting the shell search path PATH for seemingly no reason. Applied to your example:
#!/bin/bash
for (( i=6;i<=8;i++ ));
do
echo "Running $i"
/SANCFS/stats/scripts/load_cdrs.sh --debug --config /SANCFS/stats/scripts/iquall-mm4-cdr.cfg --date "2018-10-0$i" >> /home/stats/201810/load_cdrsIMRMM4-0"$i".ok 2>>/home/stats/201810/load_cdrsIMRMM4-0"$i".err
done
Don't use exec. That replaces the current process with the process that runs the specified command, so you won't repeat the loop. Just execute the command normally.
And the argument to exec shouldn't be all inside a single quoted string. Maybe you're confusing it with eval?
#!/bin/bash
PATH="/SANCFS/stats/scripts/"
for (( i=6;i<=8;i++ ));
do
echo "Running $i"
/SANCFS/stats/scripts/load_cdrs.sh --debug --config /SANCFS/stats/scripts/iquall-mm4-cdr.cfg --date 2018-10-0"$i" >> /home/stats/201810/load_cdrsIMRMM4-0"$i".ok 2>>/home/stats/201810/load_cdrsIMRMM4-0"$i".err
done
You could replace exec with dot ( . )
If you try the 5 options, you should see the different options
$ exec /bin/bash
$ /bin/bash
$ . /bin/bash
$ ./bin/bash
$ /bin/bash /bin/bash

How to execute arbitrary command under `bash -c`

What is a procedure to decorate an arbitrary bash command to execute it in a subshell? I cannot change the command, I have to decorate it on the outside.
the best I can think of is
>bash -c '<command>'
works on these:
>bash -c 'echo'
>bash -c 'echo foobar'
>bash -c 'echo \"'
but what about the commands such as
echo \'
and especially
echo \'\"
The decoration has to be always the same for all commands. It has to always work.
You say "subshell" - you can get one of those by just putting parentheses around the command:
x=outer
(x=inner; echo "x=$x"; exit)
echo "x=$x"
produces this:
x=inner
x=outer
You could (ab)use heredocs:
bash -c "$(cat <<-EOF
echo \'\"
EOF
)"
This is one way without using -c option:
bash <<EOF
echo \'\"
EOF
What you want to do is exactly the same as escapeshellcmd() in PHP (http://php.net/manual/fr/function.escapeshellcmd.php)
You just need to escape #&;`|*?~<>^()[]{}$\, \x0A and \xFF. ' and " are escaped only if they are not paired.
But beware of security issues...
Let bash take care of it this way:
1) prepare the command as an array:
astrCmd=(echo \'\");
2) export the array as a simple string:
export EXPORTEDastrCmd="`declare -p astrCmd| sed -r "s,[^=]*='(.*)',\1,"`";
3) restore the array and run it as a full command:
bash -c "declare -a astrCmd='$EXPORTEDastrCmd';\${astrCmd[#]}"
Create a function to make these steps more easy like:
FUNCbash(){
astrCmd=("$#");
export EXPORTEDastrCmd="`declare -p astrCmd| sed -r "s,[^=]*='(.*)',\1,"`";
bash -c "declare -a astrCmd='$EXPORTEDastrCmd';\${astrCmd[#]}";
}
FUNCbash echo \'\"

Bash script how to execute a command from a variable

I am trying to alter the Bash function below to execute each command argument. But when I run this script, the first echo works as intended, but the second echo that attempts to append to the scratch.txt file does not actually execute. It just gets echo'd into the prompt.
#!/bin/sh
clear
function each(){
while read line; do
for cmd in "$#"; do
cmd=${cmd//%/$line}
printf "%s\n" "$cmd"
$cmd
done
done
}
# pipe in the text file and run both commands
# on each line of the file
cat scratch.txt | each 'echo %' 'echo -e "%" >> "scratch.txt"'
exit 0
How do I get the $cmd variable to execute as a command?
I found the original code from answer 2 here:
Running multiple commands with xargs
You want eval. It's evil. Or at least, dangerous. Read all about it at BashFAQ #48.

How to invoke bash, run commands inside the new shell, and then give control back to user?

This must either be really simple or really complex, but I couldn't find anything about it... I am trying to open a new bash instance, then run a few commands inside it, and give the control back to the user inside that same instance.
I tried:
$ bash -lic "some_command"
but this executes some_command inside the new instance, then closes it. I want it to stay open.
One more detail which might affect answers: if I can get this to work I will use it in my .bashrc as alias(es), so bonus points for an alias implementation!
bash --rcfile <(echo '. ~/.bashrc; some_command')
dispenses the creation of temporary files. Question on other sites:
https://serverfault.com/questions/368054/run-an-interactive-bash-subshell-with-initial-commands-without-returning-to-the
https://unix.stackexchange.com/questions/123103/how-to-keep-bash-running-after-command-execution
This is a late answer, but I had the exact same problem and Google sent me to this page, so for completeness here is how I got around the problem.
As far as I can tell, bash does not have an option to do what the original poster wanted to do. The -c option will always return after the commands have been executed.
Broken solution: The simplest and obvious attempt around this is:
bash -c 'XXXX ; bash'
This partly works (albeit with an extra sub-shell layer). However, the problem is that while a sub-shell will inherit the exported environment variables, aliases and functions are not inherited. So this might work for some things but isn't a general solution.
Better: The way around this is to dynamically create a startup file and call bash with this new initialization file, making sure that your new init file calls your regular ~/.bashrc if necessary.
# Create a temporary file
TMPFILE=$(mktemp)
# Add stuff to the temporary file
echo "source ~/.bashrc" > $TMPFILE
echo "<other commands>" >> $TMPFILE
echo "rm -f $TMPFILE" >> $TMPFILE
# Start the new bash shell
bash --rcfile $TMPFILE
The nice thing is that the temporary init file will delete itself as soon as it is used, reducing the risk that it is not cleaned up correctly.
Note: I'm not sure if /etc/bashrc is usually called as part of a normal non-login shell. If so you might want to source /etc/bashrc as well as your ~/.bashrc.
You can pass --rcfile to Bash to cause it to read a file of your choice. This file will be read instead of your .bashrc. (If that's a problem, source ~/.bashrc from the other script.)
Edit: So a function to start a new shell with the stuff from ~/.more.sh would look something like:
more() { bash --rcfile ~/.more.sh ; }
... and in .more.sh you would have the commands you want to execute when the shell starts. (I suppose it would be elegant to avoid a separate startup file -- you cannot use standard input because then the shell will not be interactive, but you could create a startup file from a here document in a temporary location, then read it.)
bash -c '<some command> ; exec /bin/bash'
will avoid additional shell sublayer
You can get the functionality you want by sourcing the script instead of running it. eg:
$cat script
cmd1
cmd2
$ . script
$ at this point cmd1 and cmd2 have been run inside this shell
Append to ~/.bashrc a section like this:
if [ "$subshell" = 'true' ]
then
# commands to execute only on a subshell
date
fi
alias sub='subshell=true bash'
Then you can start the subshell with sub.
The accepted answer is really helpful! Just to add that process substitution (i.e., <(COMMAND)) is not supported in some shells (e.g., dash).
In my case, I was trying to create a custom action (basically a one-line shell script) in Thunar file manager to start a shell and activate the selected Python virtual environment. My first attempt was:
urxvt -e bash --rcfile <(echo ". $HOME/.bashrc; . %f/bin/activate;")
where %f is the path to the virtual environment handled by Thunar.
I got an error (by running Thunar from command line):
/bin/sh: 1: Syntax error: "(" unexpected
Then I realized that my sh (essentially dash) does not support process substitution.
My solution was to invoke bash at the top level to interpret the process substitution, at the expense of an extra level of shell:
bash -c 'urxvt -e bash --rcfile <(echo "source $HOME/.bashrc; source %f/bin/activate;")'
Alternatively, I tried to use here-document for dash but with no success. Something like:
echo -e " <<EOF\n. $HOME/.bashrc; . %f/bin/activate;\nEOF\n" | xargs -0 urxvt -e bash --rcfile
P.S.: I do not have enough reputation to post comments, moderators please feel free to move it to comments or remove it if not helpful with this question.
With accordance with the answer by daveraja, here is a bash script which will solve the purpose.
Consider a situation if you are using C-shell and you want to execute a command
without leaving the C-shell context/window as follows,
Command to be executed: Search exact word 'Testing' in current directory recursively only in *.h, *.c files
grep -nrs --color -w --include="*.{h,c}" Testing ./
Solution 1: Enter into bash from C-shell and execute the command
bash
grep -nrs --color -w --include="*.{h,c}" Testing ./
exit
Solution 2: Write the intended command into a text file and execute it using bash
echo 'grep -nrs --color -w --include="*.{h,c}" Testing ./' > tmp_file.txt
bash tmp_file.txt
Solution 3: Run command on the same line using bash
bash -c 'grep -nrs --color -w --include="*.{h,c}" Testing ./'
Solution 4: Create a sciprt (one-time) and use it for all future commands
alias ebash './execute_command_on_bash.sh'
ebash grep -nrs --color -w --include="*.{h,c}" Testing ./
The script is as follows,
#!/bin/bash
# =========================================================================
# References:
# https://stackoverflow.com/a/13343457/5409274
# https://stackoverflow.com/a/26733366/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://www.linuxquestions.org/questions/other-%2Anix-55/how-can-i-run-a-command-on-another-shell-without-changing-the-current-shell-794580/
# https://www.tldp.org/LDP/abs/html/internalvariables.html
# https://stackoverflow.com/a/4277753/5409274
# =========================================================================
# Enable following line to see the script commands
# getting printing along with their execution. This will help for debugging.
#set -o verbose
E_BADARGS=85
if [ ! -n "$1" ]
then
echo "Usage: `basename $0` grep -nrs --color -w --include=\"*.{h,c}\" Testing ."
echo "Usage: `basename $0` find . -name \"*.txt\""
exit $E_BADARGS
fi
# Create a temporary file
TMPFILE=$(mktemp)
# Add stuff to the temporary file
#echo "echo Hello World...." >> $TMPFILE
#initialize the variable that will contain the whole argument string
argList=""
#iterate on each argument
for arg in "$#"
do
#if an argument contains a white space, enclose it in double quotes and append to the list
#otherwise simply append the argument to the list
if echo $arg | grep -q " "; then
argList="$argList \"$arg\""
else
argList="$argList $arg"
fi
done
#remove a possible trailing space at the beginning of the list
argList=$(echo $argList | sed 's/^ *//')
# Echoing the command to be executed to tmp file
echo "$argList" >> $TMPFILE
# Note: This should be your last command
# Important last command which deletes the tmp file
last_command="rm -f $TMPFILE"
echo "$last_command" >> $TMPFILE
#echo "---------------------------------------------"
#echo "TMPFILE is $TMPFILE as follows"
#cat $TMPFILE
#echo "---------------------------------------------"
check_for_last_line=$(tail -n 1 $TMPFILE | grep -o "$last_command")
#echo $check_for_last_line
#if tail -n 1 $TMPFILE | grep -o "$last_command"
if [ "$check_for_last_line" == "$last_command" ]
then
#echo "Okay..."
bash $TMPFILE
exit 0
else
echo "Something is wrong"
echo "Last command in your tmp file should be removing itself"
echo "Aborting the process"
exit 1
fi
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')
In case you can't or don't want to use process substitution:
$ cat script
some_command
$ bash --init-file script
Another way:
$ bash -c 'some_command; exec bash'
$ sh -c 'some_command; exec sh'
sh-only way (dash, busybox):
$ ENV=script sh
Here is yet another (working) variant:
This opens a new gnome terminal, then in the new terminal it runs bash. The user's rc file is read first, then a command ls -la is sent for execution to the new shell before it turns interactive.
The last echo adds an extra newline that is needed to finish execution.
gnome-terminal -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'
I also find it useful sometimes to decorate the terminal, e.g. with colorfor better orientation.
gnome-terminal --profile green -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'

Resources