How to write exact string inside block instead of executing them? While using << EOF in Bash? - linux

I have a file configure.sh with following (it creates a test.sh file with configuration, so that i can use that test.sh finally as main configuration task). But it does not work
cat > /var/tmp/test.sh << EOF
regex='value=(.*)'
for i in $(cat /var/tmp/test.ini);
do
if [[ $i =~ $regex ]];
then
echo ${BASH_REMATCH[1]}
#or
curl -v ${BASH_REMATCH[1]}
fi
done
EOF
When the configure.sh is executed it makes the test.sh file completely wrong such as
reged='value(.*)'
for i in
original line1
original line1
original line1
original line1
do
if [[ =~ ]];
then
fi
done
EOF
The EOF block is not writing exactly how i set above. How do you write such string inside EOF?

Change << EOF to << 'EOF' and leave the EOF at the end unchanged. Quoting the EOF in this way will stop Bash from executing expansions on the here-document.

From man bash, section Here Documents:
No parameter expansion, command substitution, arithmetic
expansion, or pathname expansion is performed on word. If any characters in word are
quoted, the delimiter is the result of quote
removal on word, and the lines in the here-document are not expanded.
So write << \EOF instead of << EOF.

Related

Use bash to write a bash script? [duplicate]

This question already has answers here:
echo "#!" fails -- "event not found"
(5 answers)
Closed 7 months ago.
echo "#!/bin/bash\nls -l /home/" > /home/myscript.sh
bash: !/bin/bash\nls: event not found
My script should be:
#!/bin/bash
ls -l /home/
Why does it ignore the echo "" string and think that there is some sort of event? Why does it not recognize #!/bin/bash as a special word?
the same thing happens when I
echo "#!/bin/bash" > /home/myscript.sh
so it's not the new line!
echo -e "#\!/bin/bash" > /home/myscript.sh
writes the file content as:
#\!/bin/bash
Why is this simple action going miserably wrong?
From the bash manpage:
Enclosing characters in double quotes preserves the literal value of
all characters within the quotes, with the exception of $, `, \,
and, when history expansion is
enabled, !.
So either use single quotes, or disable history expansion with set +o history.
But don't use echo. Instead, do :
printf '%s\n' '#!/bin/bash' 'ls -l /home/' > /home/myscript
or
cat > /home/myscript << 'EOF'
#!/bin/bash
ls -l /home/
EOF
echo -e '#!/bin/bash\nls -l /home/' > /home/myscript.sh
a combination of -e and using single quote fixed it.

Output of a command within here-document

I have a script which contains the code block:
cat << EOF > new_script.sh
...
echo "$(pwd)" >> log.txt
...
EOF
The script new_script.sh is set to run at a later time. Bash recognizes the $(pwd) within the script and evaluates it before it looks at the entire EOF block, so the pwd of the current directory is output instead of the pwd of new_script.sh when it is run. Why is this the case (what logic does bash use to know to evaluate $(command)) and what is the best solution to this?
By adding an escape $, \$ , you can solve this issue.
cat << EOF > new_script.sh
...
echo "\$(pwd)" >> log.txt
...
EOF
Unless you put single quotes around the EOF marker, the contents of the here-doc are treated like a double-quoted string, so all variables and command substitutions are expanded immediately.
To leave them as literals, use
cat << 'EOF' > new_script.sh
...
echo "$(pwd)" >> log.txt
...
EOF

Passing variables in remote ssh command in shell script

Right now I have this file so far...
#!/usr/bin/env bash
DIRECTORY=$1
ssh root#example.com "$( cat <<'EOT'
cd /web/$DIRECTORY || exit
pwd
unset GIT_DIR
git log --oneline -n 10 --decorate
git branch
EOT
)";
Why does the pwd just print out "/web/"? It doesn't actually seem to be using my variable. Then all the git commands throw errors about being in the web directory.
If I have it echo $DIRECTORY out before ssh-ing, it echos out my variable, but refuses to pass it through to the ssh command?
This happens because you quote the here doc delimiter. Here's POSIX:
If any character in word is quoted, the delimiter is formed by performing quote removal on word, and the here-document lines will not be expanded. Otherwise, the delimiter is the word itself.
And here's an example:
#!/bin/bash
var=42
cat << 'end'
With quotes: $var and \$var
end
cat << end
Without quotes: $var and \$var
end
When executed:
With quotes: $var and \$var
Without quotes: 42 and $var

Run commands from a text file through a bash script

I am attempting to write a script that will read through a text file, and then execute every line that begins with the word "run" or "chk" as a command. This is what I have thus far:
#!/bin/bash
counter=1
for i in $#
do
while read -r line
do
if [[ ${line:0:4} == "run " ]]
then
echo "Now running line $counter"
${line:4:${#line}}
elif [[ ${line:0:4} == "chk " ]]
then
echo "Now checking line $counter"
${line:4:${#line}}
elif [[ ${line:0:2} == "# " ]]
then
echo "Line $counter is a comment"
else
echo "Line $counter: '$line' is an invalid line"
fi
counter=$((counter+1))
done<$i
done
However, when I feed it a text file with, for example the commands
run echo > temp.txt
It does not actually create a file called temp.txt, it just echoes "> temp.txt" back to the stdout. It also does a similar thing when I attempt to do something like
run program arguments > filename.txt
It does not put the output of the program in a file as I want, but it rather tries to treat the '>' as a file name.
I know this is a super specific and probably obvious thing, but I am very new to bash and all shell scripting.
Thanks
You need to use eval to do all the normal shell parsing of the variable:
eval "${line:4}"
You also don't need :${#line}. If you leave out the length, it defaults to the rest of the string.

How to read each line of a file 1 at a time in BASH [duplicate]

This question already has answers here:
Looping through the content of a file in Bash
(16 answers)
Closed 2 years ago.
I have the following .txt file:
Marco
Paolo
Antonio
I want to read it line-by-line, and for each line I want to assign a .txt line value to a variable. Supposing my variable is $name, the flow is:
Read first line from file
Assign $name = "Marco"
Do some tasks with $name
Read second line from file
Assign $name = "Paolo"
The following reads a file passed as an argument line by line:
while IFS= read -r line; do
echo "Text read from file: $line"
done < my_filename.txt
This is the standard form for reading lines from a file in a loop. Explanation:
IFS= (or IFS='') prevents leading/trailing whitespace from being trimmed.
-r prevents backslash escapes from being interpreted.
Or you can put it in a bash file helper script, example contents:
#!/bin/bash
while IFS= read -r line; do
echo "Text read from file: $line"
done < "$1"
If the above is saved to a script with filename readfile, it can be run as follows:
chmod +x readfile
./readfile filename.txt
If the file isn’t a standard POSIX text file (= not terminated by a newline character), the loop can be modified to handle trailing partial lines:
while IFS= read -r line || [[ -n "$line" ]]; do
echo "Text read from file: $line"
done < "$1"
Here, || [[ -n $line ]] prevents the last line from being ignored if it doesn't end with a \n (since read returns a non-zero exit code when it encounters EOF).
If the commands inside the loop also read from standard input, the file descriptor used by read can be chanced to something else (avoid the standard file descriptors), e.g.:
while IFS= read -r -u3 line; do
echo "Text read from file: $line"
done 3< "$1"
(Non-Bash shells might not know read -u3; use read <&3 instead.)
I encourage you to use the -r flag for read which stands for:
-r Do not treat a backslash character in any special way. Consider each
backslash to be part of the input line.
I am citing from man 1 read.
Another thing is to take a filename as an argument.
Here is updated code:
#!/usr/bin/bash
filename="$1"
while read -r line; do
name="$line"
echo "Name read from file - $name"
done < "$filename"
Using the following Bash template should allow you to read one value at a time from a file and process it.
while read name; do
# Do what you want to $name
done < filename
#! /bin/bash
cat filename | while read LINE; do
echo $LINE
done
Use:
filename=$1
IFS=$'\n'
for next in `cat $filename`; do
echo "$next read from $filename"
done
exit 0
If you have set IFS differently you will get odd results.
Many people have posted a solution that's over-optimized. I don't think it is incorrect, but I humbly think that a less optimized solution will be desirable to permit everyone to easily understand how is this working. Here is my proposal:
#!/bin/bash
#
# This program reads lines from a file.
#
end_of_file=0
while [[ $end_of_file == 0 ]]; do
read -r line
# the last exit status is the
# flag of the end of file
end_of_file=$?
echo $line
done < "$1"
If you need to process both the input file and user input (or anything else from stdin), then use the following solution:
#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
read -p "> $line (Press Enter to continue)"
done
Based on the accepted answer and on the bash-hackers redirection tutorial.
Here, we open the file descriptor 3 for the file passed as the script argument and tell read to use this descriptor as input (-u 3). Thus, we leave the default input descriptor (0) attached to a terminal or another input source, able to read user input.
For proper error handling:
#!/bin/bash
set -Ee
trap "echo error" EXIT
test -e ${FILENAME} || exit
while read -r line
do
echo ${line}
done < ${FILENAME}
Use IFS (internal field separator) tool in bash, defines the character using to separate lines into tokens, by default includes <tab> /<space> /<newLine>
step 1: Load the file data and insert into list:
# declaring array list and index iterator
declare -a array=()
i=0
# reading file in row mode, insert each line into array
while IFS= read -r line; do
array[i]=$line
let "i++"
# reading from file path
done < "<yourFullFilePath>"
step 2: now iterate and print the output:
for line in "${array[#]}"
do
echo "$line"
done
echo specific index in array: Accessing to a variable in array:
echo "${array[0]}"
The following will just print out the content of the file:
cat $Path/FileName.txt
while read line;
do
echo $line
done

Resources