BASH script to pass variables without substitution into new script - bash

As part of a system build script I have a script that creates various files and configurations.
However one part of the build script creates a new script that contains variables that I don't want resolved when the build script runs. Code snippet example
cat - > /etc/profile.d/mymotd.sh <<EOF
hostname=`uname -n`
echo -e "Hostname is $hostname"
EOF
I have tried all sorts of combinations of ' and " and ( and [ but I cannot get the script to send the content without substituting the values and placing the substitutes in the new script rather than the original text.
Ideas?

The easiest method, assuming you don't want anything to be substituted in the here doc, is to put the EOF marker in quotes, like this:
cat - > /etc/profile.d/mymotd.sh <<'EOF'
hostname=`uname -n`
echo -e "Hostname is $hostname"
EOF

Easiest is to escape the $
echo -e "Hostname is \$hostname"

Related

Script to print pass arguments to script

I am using simple script to print the arguments. But not able to do so.
i am using cat command to add content to a file.
[root#cen06gst ~]# cat<<EOF>pass.sh
echo " you have passed me" $#
> EOF
But when i am seeing the file content again using cat , this is showing
[root#cen06gst ~]# cat pass.sh
echo " you have passed me"
Cat only is a command line tool to concatenate and print to the screen, it doesn't modify the file. Read
man cat
If you want to run your script, run
./pass.sh argument
It is also good practice to start your script with a shebang:
#!/bin/bash
Without it the system doesn't know what language to use to process the script.

Bash: expand variables, but not special characters

I have a bash script that creates and executes an expect script by stitching together dozens of different files containing pieces of expect code. Those files contain environment variables that need to be expanded. Example:
expect.piece:
send "command\r"
sleep $timeout
send "command argument\r"
script.sh:
#let's try it like this
eval echo $(cat expect.piece)
#or maybe like this
eval "echo \"$(cat expect.piece)\""
output:
send command\r sleep 1 send command argument\r
send commandr
sleep 1
send command argumentr
Desired otput:
send "command\r"
sleep 1
send "command argument\r"
I need a solution without sed string substitution (there is a lot of environment variables) and without modifying original expect script files. I guess it could be done line by line, but is there a more elegant solution?
Default field separator in bash is a space so set input file separator as a new line like IFS=$(echo -e '\n') before executing eval echo $(cat expect.piece) .
Final script would be :
#Storing original field separator in variable OFS
OFS=$IFS
#Setting IFS as new line using echo -e. Unfortunately IFS="\n" does not work in bash
IFS=$(echo -e '\n')
eval echo $(cat expect.piece)
#Resetting the field separator as space
IFS=$OFS
There is one another way which you can put your expect code with -c flag in the shell script as shown below.
script.sh
#Calling the expect.piece file code here
expect -c expect.piece
You can make use of the optional command line values as ,
expect -c "set count 1" myscript.exp
where the variable count will be used in the expect script file myscript.exp.
You can directly give the whole code as
expect -c "
send \"command\r\"
sleep $timeout
send \"command argument\r\"
"
Notice the use of backslash to escape the double quotes wherever needed. Single quotes can also be used. But, if you use double quotes, then only shell substitution can happen.
Please refer here to know more about the -c flag in expect.
It's not clear from your question in what context you want this output. If it's okay to embed the Expect script as a here document, what you want is trivial.
#!/bin/sh
timeout=1
cat <<____HERE
send "command\r"
sleep $timeout
send "command argument\r"
____HERE
(Maybe you can even replace the cat with expect but I'm not familiar enough with Expect to make any recommendations.)
If you need to take the input from a file, and only have a limited set of variables you want expanded, you could do that with sed.
sed "s/\$timeout/$timeout/g" file
If you need a more general solution, you might want to swich to Perl:
perl -pe 's/\$(\w+)/$ENV{$1} || "\$$1" /ge' file
but this requires you to export (or otherwise expose to Perl) the shell environment variables you want exported. (This will just not substitute any undefined variables; change the value after || if you want to change that aspect of the behavior.)
I invented the solution for this problem, it is a kludge, but it works.
expect.piece:
sleep $timeout
send "foo bar\r"
send "$(date)\r"
script.sh:
timeout=1
eval echo $(sed " \
s/\\\/\\\\\\\/g; \
s/\"/\\\\\"/g; \
s/\"/\\\\\`/\"/\\\\\`/g; \
" "expect.piece" | tr '\n' '+') | tr '+' '\n'
output:
sleep 1
send "foo bar\r"
send "Wed Feb 18 03:19:24 2015\r"
First, we need to escape all the backslashes, backticks and quotes in the file, because they will be removed during the evaluation. Then, we need to replace all the newline characters with pluses, in order to make it in a single line. After that, we evaluate that line (the evaluation will "expand" all the environment variables and command substitutions) and replace pluses back to newlines.

I need help echoing a variable using bash

I'm trying to append the line
log-bin="/mysql-log/bin-log"
to the file
/etc/mysql/test
and the following command does the job
echo 'echo "log-bin=""\"/mysql-log/bin-log\"" >> /etc/mysql/test' | sudo -s
However, I'm having a hard time if I want to have a more flexible script where the line to append is store on a variable. That is by doing something like this:
#!/bin/bash
BIN_LOG_DIR="\"/mysql-log/bin-log\""
str="log-bin="$BIN_LOG_DIR
echo 'echo ${str} >> /etc/mysql/test' | sudo -s
This script adds an empty line rather that the intended value contained in the variable $str. How can I solve this problem?
Thanks in advance!!
gorf
When you use single quotes, the variables are nor interpolated, you need double quotes. That's the problem here
So :
#!/bin/bash
BIN_LOG_DIR="/mysql-log/bin-log"
str="log-bin$BIN_LOG_DIR"
echo "echo ${str} >> /etc/mysql/test" | sudo -s
See http://mywiki.wooledge.org/BashGuide/Practices#Quoting for explanations about the use of the different quotes.

Store command to file

I'd like to create a script A which creates a script B. Script B creates a directory. So I created a file with this content, grant x permission to it, then execute it. Unfortunately it doesn't run as I expect. It makes directory first then create an empty file. Why?
#!/bin/bash
batch=`mkdir /home/hieund/bpl`
echo $batch > newfile
Update:
After trying your solution, I have:
#!/bin/bash
$myPath=$HOME/bpl
batch='mkdir ' $myPath
echo $batch > newfile
It doesn't work as well. Same unexpected behavior.
Update:
#!/bin/bash
$myPath=$HOME/bpl
batch="mkdir $myPath"
echo $batch > newfile
It doesn't work too. Same unexpected behavior.
bash: /home/hieund/bpl=/home/hieund/bpl: No such file or directory
It makes a directory at the moment of assignment, because you said this with "command substitution"
batch=`mkdir /home/hieund/bpl`
The flow of execution
mkdir /home/... - creates the directory - because of backticks - command substitution
the mkdir returns nothing, therefore
the assignment is like batch= (it assigns nothing)
the echo $batch echoes the "nothing" so: echo > newfile
you should to use
batch='mkdir /home/hieund/bpl'
for embedding variable use double quotes
batch="mkdir $myPath"
You always can use the bash -x script - to show what is executing. E.g. having a script myscript.sh
#!/bin/bash
MYDIR="./somedir"
batch="mkdir $MYDIR"
echo "$batch" > newfile
the command
bash -x myscript.sh
will show the execution of command and arguments. (note, not shown redirections)
+ MYDIR=./somedir
+ batch='mkdir ./somedir'
+ echo 'mkdir ./somedir'
One comment: You should generally assign things to variables with double quotes, because you can avoid problems with spaces. Note
myvar=$VAR/some
and
myvar="$VAR/some"
makes a big difference when the $VAR contains spaces.
Because backticks are not used for strings.
batch="mkdir /home/hieund/bpl"

How to expand shell variables in a text file?

Consider a ASCII text file (lets say it contains code of a non-shell scripting language):
Text_File.msh:
spool on to '$LOG_FILE_PATH/logfile.log';
login 'username' 'password';
....
Now if this were a shell script I could run it as $ sh Text_File.msh and the shell would automatically expand the variables.
What I want to do is have shell expand these variables and then create a new file as Text_File_expanded.msh as follows:
Text_File_expanded.msh:
spool on to '/expanded/path/of/the/log/file/../logfile.log';
login 'username' 'password';
....
Consider:
$ a=123
$ echo "$a"
123
So technically this should do the trick:
$ echo "`cat Text_File.msh`" > Text_File_expanded.msh
...but it doesn't work as expected and the output-file while is identical to the source.
So I am unsure how to achieve this.. My goal is make it easier to maintain the directory paths embedded within my non-shell scripts. These scripts cannot contain any UNIX code as it is not compiled by the UNIX shell.
This question has been asked in another thread, and this is the best answer IMO:
export LOG_FILE_PATH=/expanded/path/of/the/log/file/../logfile.log
cat Text_File.msh | envsubst > Text_File_expanded.msh
if on Mac, install gettext first: brew install gettext
see:
Forcing bash to expand variables in a string loaded from a file
This solution is not elegant, but it works. Create a script call shell_expansion.sh:
echo 'cat <<END_OF_TEXT' > temp.sh
cat "$1" >> temp.sh
echo 'END_OF_TEXT' >> temp.sh
bash temp.sh >> "$2"
rm temp.sh
You can then invoke this script as followed:
bash shell_expansion.sh Text_File.msh Text_File_expanded.msh
If you want it in one line (I'm not a bash expert so there may be caveats to this but it works everywhere I've tried it):
when test.txt contains
${line1}
${line2}
then:
>line1=fark
>line2=fork
>value=$(eval "echo \"$(cat test.txt)\"")
>echo "$value"
line1 says fark
line2 says fork
Obviously if you just want to print it you can take out the extra value=$() and echo "$value".
If a Perl solution is ok for you:
Sample file:
$ cat file.sh
spool on to '$HOME/logfile.log';
login 'username' 'password';
Solution:
$ perl -pe 's/\$(\w+)/$ENV{$1}/g' file.sh
spool on to '/home/user/logfile.log';
login 'username' 'password';
One limitation of the above answers is that they both require the variables to be exported to the environment. Here's what i came up with that would allow the variables to be local to the current shell script:
#!/bin/sh
FOO=bar;
FILE=`mktemp`; # Let the shell create a temporary file
trap 'rm -f $FILE' 0 1 2 3 15; # Clean up the temporary file
(
echo 'cat <<END_OF_TEXT'
cat "$#"
echo 'END_OF_TEXT'
) > $FILE
. $FILE
The above example allows the variable $FOO to be substituted in the files named on the command line. I'm sure it can be improved, but this works for me so far.
Thanks to both previous answers for their ideas!
If the variables you want to translate are known and limited in number, you can always do the translation yourself:
sed "s/\$LOG_FILE_PATH/$LOG_FILE_PATH/g" input > output
And also assuming the variable itself is already known
This solution allows you to keep the same formatting in the ouput file
Copy and paste the following lines in your script
cat $1 | while read line
do
eval $line
echo $line
eval echo $line
done | uniq | grep -v '\$'
this will read the file passed as argument line by line, and then process to try and print each line twice:
- once without substitution
- once with substitution of the variables.
then remove the duplicate lines
then remove the lines containing visible variables ($)
Yes eval should be used carefully, but it provided me this simple oneliner for my problem. Below is an example using your filename:
eval "echo \"$(<Text_File.msh)\""
I use printf instead of echo for my own purposes, but that should do the trick. Thank you abyss.7 providing the link that solve my problem. Hope it helps.
Create an ascii file test.txt with the following content:
Try to replace this ${myTestVariable1}
bla bla
....
Now create a file “sub.sed” containing variable names, eg
's,${myTestVariable1},'"${myTestVariable1}"',g;
s,${myTestVariable2},'"${myTestVariable2}"',g;
s,${myTestVariable3},'"${myTestVariable3}"',g;
s,${myTestVariable4},'"${myTestVariable4}"',g'
Open a terminal move to the folder containing test.txt and sub.sed.
Define the value of the varible to be replaced
myTestVariable1=SomeNewText
Now call sed to replace that variable
sed "$(eval echo $(cat sub.sed))" test.txt > test2.txt
The output will be
$cat test2.txt
Try to replace this SomeNewText
bla bla
....
#logfiles.list:
$EAMSROOT/var/log/LinuxOSAgent.log
$EAMSROOT/var/log/PanacesServer.log
$EAMSROOT/var/log/PanacesStrutsGUI.log
#My Program:
cat logfiles.list | while read line
do
eval Eline=$line
echo $Eline
done

Resources