Why can't pass the variable's value into file in /etc directory? - bash

I want to pass the value of the $ip variable into the file /etc/test.json with bash.
ip="xxxx"
sudo bash -c 'cat > /etc/test.json <<EOF
{
"server":"$ip",
}
EOF'
I expect the content of /etc/test.json to be
{
"server":"xxxx",
}
However the real content in /etc/test.json is:
{
"server":"",
}
But if I replace the target directory /etc/ with /tmp
ip="xxxx"
cat > /tmp/test.json <<EOF
{
"server":"$ip",
}
EOF
the value of the $ip variable gets passed into /tmp/test.json:
$ cat /tmp/test.json
{
"server":"xxxx",
}
In Kamil Cuk's example, the subprocess is cat > /etc/test.json which contains no variable.
sudo sh -c 'cat > /etc/test.json' << EOF
{
"server":"$ip",
}
EOF
It does not export the $ip variable at all.
Now let's make an analysis for the following:
ip="xxxx"
sudo bash -c "cat > /etc/test.json <<EOF
{
"server":\""$ip"\",
}
EOF"
The different parts in
"cat > /etc/test.json <<EOF
{
"server":\""$ip"\",
}
EOF"
will concatenate into a long string and as a command .Why can the $ip variable inherit the value from its father process here?

There are two reasons for this behavior:
Per default, variables are no passed to the environment of subsequently executed commands.
The variable is not expanded in the current context, because your command is wrapped in single quotes.
Exporting the variable
Place an export statement before the variable, see man 1 bash
The supplied names are marked for automatic export to the environment of subsequently executed commands.
And as noted by Léa Gris you also need to tell sudo to preserve the environment with the -E or --preserve-environment flag.
export ip="xxxx"
sudo -E bash -c 'cat > /etc/test.json <<EOF
{
"server":"$ip",
}
EOF'
Expand the variable in the current context:
This is the reason your second command works, you do not have any quotes around the here document in this example.
But if I replace the target directory /etc/ with /tmp [...] the value of the $ip variable gets passed into /tmp/test.json
You can change your original snippet by replacing the single quotes with double quotes and escaping the quotes around your ip:
ip="xxxx"
sudo bash -c "cat > /etc/test.json <<EOF
{
"server":\""$ip"\",
}
EOF"
Edit: For your additional questions:
In Kamil Cuk's example, the subprocess is cat > /etc/test.json which contains no variable.
sudo sh -c 'cat > /etc/test.json' << EOF
{
"server":"$ip",
}
EOF
It does not export the $ip variable at all.
Correct and you did not wrap the here document in single quotes. Therefore $ip is substituted in the current context and the string passed to subprocesses standard input is
{
"server":"xxxx",
}
So in this example the subprocess does not need to know the $ip variable.
Simple example
$ x=1
$ sudo -E sh -c 'echo $x'
[sudo] Password for kalehmann:
This echos nothing because
'echo $x' is wrapped in single quotes. $x is therefore not substituted in the current context
$x is not exported. Therefore the subprocess does not know its value.
$ export y=2
$ sudo -E sh -c 'echo $y'
[sudo] Password for kalehmann:
2
This echos 2 because
'echo $y' is wrapped in single quotes. $x is therefore not substituted in the current context
$y is exported. Therefore the subprocess does know its value.
$ z=3
$ sudo -E sh -c "echo $z"
[sudo] Password for kalehmann:
3
This echos 3 because
"echo $z" is wrapped in double quotes. $z is therefore substituted in the current context

There little need to do the here document inside the subshell. Just do it outside.
sudo tee /etc/test.json <<EOF
{
"server":"$ip",
}
EOF
or
sudo sh -c 'cat > /etc/test.json' << EOF
{
"server":"$ip",
}
EOF

Generally, it is not safe to build a fragment of JSON using string interpolation, because it requires you to ensure the variables are properly encoded. Let a tool like jq to that for you.
Pass the output of jq to tee, and use sudo to run tee to ensure that the only thing you do as root is open the file with the correct permissions.
ip="xxxx"
jq --arg x "$ip" '{server: $x}' | sudo tee /etc/test.json > /dev/.null

Related

How to get a bash variable from inside postgre's?

I'm kind of new in bash script and postgresql.
I saw in another question a way to run a bash script as psql user here.
I tried making a bash function as follow,
postgres_create_db(){
sudo su postgres <<- EOF
if psql -lqt | cut -d \| -f 1 | grep -qw nokia_aaa_poc_db; then
psql -c '\dt'
else
psql -c 'CREATE DATABASE nokia_AAA_poc_db;'
fi
EOF
exit
}
where this function will be called further in code, but I wonder if I can add a RETURN to the function that's actualy returning a varible that was first declared inside postgres bash (in between the EOF's). Like bellow:
postgres_create_db(){
sudo su postgres <<- EOF
if psql -lqt | cut -d \| -f 1 | grep -qw nokia_aaa_poc_db; then
psql -c '\dt'
exists=1 #where thats a variable that I want to access outside the postgres bash.
else
psql -c 'CREATE DATABASE nokia_AAA_poc_db;'
fi
EOF
exit
return exists
}
but it gives an error on shellcheck
return exists
^-- SC2152: Can only return 0-255. Other data should be written to stdout.
Functions in bash can only return values from 0 to 255 where 0 is success. Reference: Return value in a Bash function
So you can echo the variable like this instead:
#!/usr/bin/env bash
postgres_test() {
psql -c '\dt' &> /dev/null
declare exists=1
echo $exists
}
printf "%s\n" "$(postgres_test)"
This prints "1".
You'll also notice that I redirected the output of the Postgres command to /dev/null. This is because it would be combined in the function's output otherwise.
You might wish to redirect that output to a file instead.

How to set a bash variable in a compound xargs statement

I am looking for a way to set a variable in the statements passed to xargs. The value is to be manipulated in one of the commands. Using a file or another utility is an option but I am not sure why setting the bash variable in the sequence is always coming up as empty.
$ ls c*txt
codebase.txt consoleText.txt
$ ls c*txt | xargs -i bash -c "echo processing {}; v1={} && echo ${v1/txt/file}"
codebase.txt consoleText.txt
processing codebase.txt
processing consoleText.txt
The example above distills the question to the basics. I was expecting the behavior to be something like this but inline:
$ fname=codebase.txt; echo ${fname/txt/file}
codebase.file
Thank you.
This line is resolving ${v1/txt/file} to a value before the command is executed:
$ ls c*txt | xargs -i bash -c "echo processing {}; v1={} && echo ${v1/txt/file}"
And that means the bash -c doesn't even see ${v1/txt/file}
In this line the single quotes inhibit the variable substitution so echo processing {}; v1={} && echo ${v1/txt/file} is actually passed to bash -c as a parameter:
$ ls c*txt | xargs -i bash -c 'echo processing {}; v1={} && echo ${v1/txt/file}'
You could accomplish the same thing by escaping the dollar sign:
$ ls c*txt | xargs -i bash -c "echo processing {}; v1={} && echo \${v1/txt/file}"

Transferring variable into a shell function

Is there a way to use shell function that accepts variable from the bash script (or rather, transfer a variable into a shell function)?
The following procedure works just fine (Note, I'm using this procedure as part of my need to implement output redirection as explained here):
mycmd() { cat <(head -3 MyProgrammingBook.txt|awk -F "\t" '{OFS="\t"}{print "Helloworld",$0}') > outputfile.txt; };
export -f mycmd;
bsub -q short "bash -c mycmd"
However, I would like to provide the initial file name as a variable and not as hardcoded name, something such as the following, but the following doesn't work:
myinputfile="MyProgrammingBook.txt";
mycmd() { cat <(head -3 ${myinputfile}|awk -F "\t" '{OFS="\t"}{print "Helloworld",$0}') > outputfile.txt; };
export -f mycmd;
bsub -q short "bash -c mycmd"
Ultimately, mycmd() would be called inside a loop and will be utilized each time with a different variable.
export the variable too:
myinputfile="MyProgrammingBook.txt";
mycmd() { cat <(head -3 ${myinputfile}|awk -F "\t" '{OFS="\t"}{print "Helloworld",$0}') > outputfile.txt; };
export -f mycmd;
export myinputfile; # Here
bsub -q short "bash -c mycmd"

BASH - how echo works inside EOF tags

I would like to execute the followings:
PASSWORD="mypassword"
RUNCOMMAND=$(cat <<EOF
echo $PASSWORD | sudo -S sudo echo "this is it babe"
EOF
)
But instead of this is it babe, I get the following result:
mypassword | sudo -S sudo echo "this is it babe"
I tried with cat <<\EOF, cat <<'EOF' still no luck.
Any ideas?
You are confusing a heredoc with a pipeline.
heredoc with variable expansion:
cat <<EOF
some text, possibly with variables: ${HOME} / $(whoami)
EOF
some text, possibly with variables: /home/attie / attie
heredoc without variable expansion:
cat <<"EOF"
some text, possibly with variables: ${HOME} / $(whoami)
EOF
some text, possibly with variables: ${HOME} / $(whoami)
pipeline with variable expansion (note the quotes, "):
echo "some text, possibly with variables: ${HOME} / $(whoami)" | cat
some text, possibly with variables: /home/attie / attie
pipeline without variable expansion (note the quotes, '):
echo 'some text, possibly with variables: ${HOME} / $(whoami)' | cat
some text, possibly with variables: ${HOME} / $(whoami)
${...} expands an environment variable
$(...) runs a command, and substitutes its stdout
It also looks like you're trying to have your password entered into sudo - this won't work, as sudo will repoen the terminal to acquire your password, before passing it's stdin to the final application.
You are starting from a false premise, that eval $RUNCOMMAND is something you should do. It is not; variables are for data, functions are for code.
run_command () {
docker_run_options=(
--restart=always
--name "${USER_NAME}_$(date +%Y%m%d-%H%M%S)"
-d
-e "VIRTUAL_HOST=$USER_VIRTUAL_HOST"
-e "VIRTUAL_PORT=$USER_VIRTUAL_PORT"
-e "PORT=$USER_VIRTUAL_PORT"
-p "$USER_VIRTUAL_PORT:$USER_VIRTUAL_PORT"
)
echo "$1" | sudo -S sudo docker run "${docker_run_options[#]}" "$USER_IMAGE"
}
fun_run_command () {
run_command "PASSWORD"
}
The final solution is rather simple:
PASSWORD="mypassword"
RUNCOMMAND=$(cat <<EOF
echo $PASSWORD | sudo -S sudo echo "this is it babe"
EOF
)
And execute it via eval:
eval $RUNCOMMAND
Sorry for stealing your times with this obvious problem guys:)
The usecase for the above is to echo a given command before really executing it.
Like this:
fun_run_command(){
# execute the final command
echo `eval $RUNCOMMAND`
}
fun_echo_command(){
# echo the command which will be launched (fun_run_command())
echo ${RUNCOMMAND//$PASSWORD/PASSWORD}
}
RUNCOMMAND=$(cat <<EOF
echo $PASSWORD | sudo -S sudo docker run --restart=always \
--name ${USER_NAME}_`date +%Y%m%d-%H%M%S` \
-d \
-e "VIRTUAL_HOST=$USER_VIRTUAL_HOST" \
-e "VIRTUAL_PORT=$USER_VIRTUAL_PORT" \
-e "PORT=$USER_VIRTUAL_PORT" \
-p $USER_VIRTUAL_PORT:$USER_VIRTUAL_PORT \
$USER_IMAGE
EOF
)
As you can see the command what I launch is quite long,
so it is always make sense to doublecheck what is executed by the script.
Having copy&paste the same command to multiple function is prone to error.

Pipe commands in bash function

When you define a bash function you can call bash commands with command command.
function ls() {
clear
command ls "$#"
}
How would you pipe commands in bash function?
e.g.
function ls() {
clear
command ls "$#" | head
}
EDIT: The output would be OK, but there is --color=auto. Look here
Try this in your ~/.bashrc
function ls() { clear ; builtin ls "$#" | head ; }
It's similar to the function you have already but with the inclusion of builtin, it guarantees not to get stuck in a loop calling itself. Hope this works!
EDIT: It should be noted that any colour information produced by ls with the --color=auto option won't be carried through the pipe to head.
You can pipe the colour information generated by the ls command to head if you run ls in a so-called pseudo terminal (so that ls thinks it is writing its output to a terminal, and not a pipe). This can be achieved by using the script command.
ls() {
type -P command 1>/dev/null ||
{ echo 'No "command" executable found!'; return 1; }
clear
script -q /dev/null command ls -G "$#" | tr -d '\r' | head
}
cat /usr/bin/command # on Mac OS X 10.6.8
#!/bin/sh
# $FreeBSD: src/usr.bin/alias/generic.sh,v 1.2 2005/10/24 22:32:19 cperciva Exp $
# This file is in the public domain.
builtin `echo ${0##*/} | tr \[:upper:] \[:lower:]` ${1+"$#"}
For more information see: ls command operating differently depending on recipient

Resources