How to wrap a bash script so that it will correctly handle quotes and boolean operators - linux

I am trying to create a wrapper command that will:
move to a different folder
change the current user
clear env variables
So far I created this /bin/app:
#!/bin/bash
cd /var/www/backend
if [[ $# == 0 ]];
then
exec chpst -u application env -i PATH="$PATH" /bin/bash
else
exec chpst -u application env -i PATH="$PATH" "$#"
fi
But I keep getting errors when running this commands:
app bin/console d:m:m
app bin/console d:m:m && sleep 10
app bin/console acme:bar "multi word data" "another multi word data"
First command just runs bin/console without parameters.
The second command shows error && command not found.
I also need support for cases like in third command.
What is the correct way to create a wrapper that works with more complex commands?
If this changes anything I need those to work inside docker eg.
docker-compose exec app app bin/console d:m:m
docker run acme/foo app bin/console d:m:m

This would work for you
#!/bin/bash
cd /var/www/backend || exit 1
# Pass argument 1 as shell script path and default to /bin/bash if none
shell_path="${1:-/bin/bash}"
shift
# Collect argument 2 and following arguments as shell script
shell_script="$*"
exec chpst -u application env -i PATH="$PATH" "$shell_path" -c "$shell_script"
Example usage:
app bin/console 'acme:bar "multi word data" "another multi word data"'
Another much cleaner option would be to pass the shell script as stdinput or a filename:
#!/bin/bash
cd /var/www/backend || exit 1
# Pass argument 1 as shell script path and default to /bin/bash if none
shell_path="${1:-/bin/bash}"
shift
# collect file name as argument 2. Use - for stdin if no file name
shell_file="${1:--}"
exec chpst -u application env -i PATH="$PATH" "$shell_path" <(
cat "$shell_file"
)
Example usage:
# here-string stdin
app bin/console <<<'acme:bar "multi word data" "another multi word data"'
Stored in a file console.script:
acme:bar "multi word data" "another multi word data"
Then:
app bin/console console.script

Related

Executing `sh -c` in a bash script

I have a test.sh file which takes as a parameter a bash command, it does some logic, i.e. setting and checking some env vars, and then executes that input command.
#!/bin/bash
#Some other logic here
echo "Run command: $#"
eval "$#"
When I run it, here's the output
% ./test.sh echo "ok"
Run command: echo ok
ok
But the issue is, when I pass something like sh -c 'echo "ok"', I don't get the output.
% ./test.sh sh -c 'echo "ok"'
Run command: sh -c echo "ok"
%
So I tried changing eval with exec, tried to execute $# directly (without eval or exec), even tried to execute it and save the output to a variable, still no use.
Is there any way to run the passed command in this format and get the ourput?
Use case:
The script is used as an entrypoint for the docker container, it receives the parameters from docker CMD and executes those to run the container.
As a quickfix I can remove the sh -c and pass the command without it, but I want to make the script reusable and not to change the commands.
TL;DR:
This is a typical use case (perform some business logic in a Docker entrypoint script before running a compound command, given at command line) and the recommended last line of the script is:
exec "$#"
Details
To further explain this line, some remarks and hyperlinks:
As per the Bash user manual, exec is a POSIX shell builtin that replaces the shell [with the command supplied] without creating a new process.
As a result, using exec like this in a Docker entrypoint context is important because it ensures that the CMD program that is executed will still have PID 1 and can directly handle signals, including that of docker stop (see also that other SO answer: Speed up docker-compose shutdown).
The double quotes ("$#") are also important to avoid word splitting (namely, ensure that each positional argument is passed as is, even if it contains spaces). See e.g.:
#!/usr/bin/env bash
printargs () { for arg; do echo "$arg"; done; }
test0 () {
echo "test0:"
printargs $#
}
test1 () {
echo "test1:"
printargs "$#"
}
test0 /bin/sh -c 'echo "ok"'
echo
test1 /bin/sh -c 'echo "ok"'
test0:
/bin/sh
-c
echo
"ok"
test1:
/bin/sh
-c
echo "ok"
Finally eval is a powerful bash builtin that is (1) unneeded for your use case, (2) and actually not advised to use in general, in particular for security reasons. E.g., if the string argument of eval relies on some user-provided input… For details on this issue, see e.g. https://mywiki.wooledge.org/BashFAQ/048 (which recaps the few situations where one would like to use this builtin, typically, the command eval "$(ssh-agent -s)").

exporting list of variables in docker run

We have a set of variables in env file as given below
examples.env
A="/path1"
B="/path2":$A
Now, docker run cannot substitute $B for /path/path1, due to its limitations
So, I want to export the variable in launcher script and then call those variable using -e flag, as given below
mydocker.sh
input="examples.env"
while IFS= read -r line
do
export $line
done < "$input"
docker run --rm -e <Some code> centos8
Now how to create docker command to get all the variables?
Following docker command works
docker run --rm -e A -e B centos8
But If the number of variables in examples.env file is unknown, then how can we generate docker run command?
Source the variables file in your mydocker.sh script insted of export and concat each variable with --env, at the and eval the concatenated string to variable so the variables will interpreted.
Here is an example:
# Source the variables file so they will be available in current script.
. ./examples.env
# Define docker env string it will lokk like below:.
# --env A=/path1 --env B=/path1/path2
dockerenv=""
input="examples.env"
while IFS= read -r line
do
dockerenv="${dockerenv} --env $line"
done < "$input"
# Evaluate the env string so the variables in it will be interpreted
dockerenv=$(eval echo $dockerenv)
docker run --rm $dockerenv centos8
P.S.
You need the --env insted of -e becouse -e will be interpreted as echo command argument.

How environment variables can be read from a file when going into a login shell with here-string and using the sudo command?

I am trying to write a small script that aims to login to a remote server, load environment variables and print one of them. (In the actual script, instead of an echo, the parameters that are read are to be used. For the sake of simplicity here I am using just echo.)
The structure of the script and the commands that I tried are as follows but unfortunately none succeeds:
ssh -i lightsail.pem ubuntu#production <<< '
sudo echo $TEST_PARAMETER
sudo sh -c "~/Environment/environment-variables.sh && echo $TEST_PARAMETER"
sudo bash -c "~/Environment/environment-variables.sh && echo $TEST_PARAMETER"
sudo bash -c "source ~/Environment/environment-variables.sh && echo $TEST_PARAMETER"
sudo bash <<< "source ~/Environment/environment-variables.sh && echo $TEST_PARAMETER"
';
How environment variables can be read from a file when going into a login shell with here-string and using the sudo command?
If your environment variable is set for ubuntu and not root you will need to use sudo -E
-E Indicates to the security policy that the user wishes to preserve their existing environment variables

Bash syntax issue, 'syntax error: unexpected "do" (expecting "fi")'

I have a sh script that I am using on Windows and Mac/Linux machines, and seems to work with no issues normally.
#!/bin/bash
if [ -z "$jmxname" ]
then
cd ./tests/Performance/JMX/ || exit
echo "-- JMX LIST --"
# set the prompt used by select, replacing "#?"
PS3="Use number to select a file or 'stop' to cancel: "
# allow the user to choose a file
select jmxname in *.jmx
do
# leave the loop if the user says 'stop'
if [[ "$REPLY" == stop ]]; then break; fi
# complain if no file was selected, and loop to ask again
if [[ "$jmxname" == "" ]]
then
echo "'$REPLY' is not a valid number"
continue
fi
# now we can use the selected file, trying to get it to run the shell script
rm -rf ../../Performance/results/* && cd ../jmeter/bin/ && java -jar ApacheJMeter.jar -Jjmeter.save.saveservice.output_format=csv -n -t ../../JMX/"$jmxname" -l ../../results/"$jmxname"-reslut.jtl -e -o ../../results/HTML
# it'll ask for another unless we leave the loop
break
done
else
cd ./tests/Performance/JMX/ && rm -rf ../../Performance/results/* && cd ../jmeter/bin/ && java -jar ApacheJMeter.jar -Jjmeter.save.saveservice.output_format=csv -n -t ../../JMX/"$jmxname" -l ../../results/"$jmxname"-reslut.jtl -e -o ../../results/HTML
fi
I am now trying to do some stuff with a Docker container and have used a node:alpine image, as the rest of my project is NodeJS based, but for some reason the script will not run in the Docker container giving the following -
line 12: syntax error: unexpected "do" (expecting "fi")
How can I fix that? The script seems to be working for every system it's been run on so far, and not thrown up any issues.
The error message indicates that the script is executed as '/bin/sh', and not as /bin/bash. You can see the message with '/bin/sh -n script.sh'
Check how the script is invoked. On different systems /bin/sh is symlinked to bash or other shell that is less feature rich.
In particular, the problem is with the select statement, included in bash, but not part of the POSIX standard.
Another option is that bash on your docker is set to be POSIX compliant by default
#dash-o was correct, and adding -
RUN apk update && apk add bash
to my dockerfile added bash into the container and now it works fine :)

Start a Pythonscript in a Screen session

i am currently working on a little bash script to start a .py file in a Screen session and could use help.
I have these 2 Files:
test.py (located at /home/developer/Test/):
import os
print("test")
os.system("ping -c 5 www.google.de>>/home/developer/Test/test.log")
test.sh (located at /home/developer/):
#!/bin/bash
Status="NULL"
if ! screen -list | grep -q "foo";
then
Status="not running"
else
Status="running"
fi
echo "Status: $Status"
read -p "Press [Enter] key to start/stop."
if [[ $Status == "running" ]]
then
screen -S foo -p 0 -X quit
echo "Stopped Executing"
elif [[ $Staus == "not running" ]]
then
screen -dmS foo sh
screen -S foo -X python /home/developer/Test/test.py
echo "Created new Instance"
else
exit 1
fi
It works as intendet until it has to start the python script aka. this line:
screen -S foo -X python /home/developer/Test/test.py
when running it in my normal shell i get:
test
sh: 1: cannot create /home/developer/Test/test.log: Permission denied
MY Questions:
I understand the cause of the Permission denied case (works with sudo) but how do i give Permissions and more interestingly, to whom do i give the Permissions to? (python? | screen? | myuser?)
Is the line to create a new instance in which the script runs correct like that?
Can u think of a better way to execute a python script which has to run night and day but is start and stoppable and doesn't block the shell?
To answer your questions:
You should not need to use sudo at all if the proper user/group is set on the scripts.
$ chmod 644 <user> <group> <script name>
The line creating the new instance does not look correct, it should be more like:
screen -S foo -d -m /usr/bin/python /home/Developer/Test/test.py
While using full path to the python exec; remove useless preceding line: screen -dmS foo sh
Screen is more than adequte to prefer such tasks.
Other problems in your script:
Add a shebang to the python script (eg. #!/usr/bin/python)
Typo on line 20 of test.sh: should be $Status, not $Staus
You may need to initially create test.log before executing your script (eg. touch test.log)

Resources