eval echo "$line" ignoring '&' symbol - bash

I am using the below portion in my script to replace variables in a file.
But after running this portion of the script , the "&" symbol is ignored by the script. Is there a reason for this behaviour ?
while read line
do
eval echo "$line" >> output.txt
done < "input"
My input file looks like below
XXX.6.ID=LCPR_PROJ&
XXX.6.VALUE=$PMS&
XXX.6.CTID=58&
XXX.6.ECID=1032&
XXX.6.SEC=
After running the command , my output looks like below
XXX.6.ID=LCPR_PROJ
XXX.6.VALUE=132
XXX.6.CTID=58
XXX.6.ECID=1032
XXX.6.SEC=
Do I need to alter something in my command ?

You don't want to use eval!
eval will evaluate the expression separately, and interpret the & character.
The & character tells bash to run the command in a subshell. From the Bash man page:
If a command is terminated by the control operator &, the shell executes the command in the background in a subshell.
Remove it from your script
while read line
do
echo "$line" >> output.txt
done < "input"
and it will work as expected.
If you do want to run every line through eval, and you know what you're doing, then you will have to manually detect and escape the &s:
while read line; do
line=${line/&/\\&}
eval echo "$line";
done
By using bash's string manipulation.

Related

Read commands from test file and execute

I am trying to write a shell testing program which compares the output for my program with the sample program. I have stored a list of command in a text file, it looks like this:
commands.txt:
echo line A > a
echo line A > b
./program a b
and the shell test looks like this:
cat $testname | while read LINE
do
echo -e "$LINE$"
$LINE
done
but rather than crating files a and b the program produces the flowing:
echo line A > a
line A > a
echo line B > b
line B > b
How can I execute the command just like it was written in the shell file and redirect the out put to another file?
I think the only way to do that is to use eval:
cat "$testname" | while read -r; do
echo "$REPLY"
eval "$REPLY"
done
If you just run $LINE, it will perform word splitting, but not I/O redirection, so it'll just pass > as a normal argument to echo.
The shell processes redirections before word expansion, which means that the > inside the string is not interpreted by the shell in this context. You need to request explicitly that the string is interpreted as a full command, like this:
eval "$LINE"
If you would like to write the exact same lines inside of the commands.txt file, into another file, you can say;
echo "$line" >> WriteTheLines.txt
If you would like to execute the commands inside of the commands.txt file, and write the output of the commands into another file, you can say;
eval "$line" >> ExecuteTheCommands.txt
So as an example;
#!/bin/bash
input="/home/commands.txt"
while read line
do
echo "$line" >> WriteTheCommands.txt
eval "$line" >> ExecuteTheCommands.txt
done<"$input"

Unix Bash content of a file as argument stops at first line

I'm having an issue in something that seems to be a rookie error, but I can't find a way to find a solution.
I have a bash script : log.sh
which is :
#!/bin/bash
echo $1 >> log_out.txt
And with a file made of filenames (taken from the output of "find" which names is filesnames.txt and contains 53 lines of absolute paths) I try :
./log.sh $(cat filenames.txt)
the only output I have in the log_out.txt is the first line.
I need each line to be processed separately as I need to put them in arguments in a pipeline with 2 softwares.
I checked for :
my lines being terminated with /n
using a simple echo without writing to a file
all the sorts of cat filenames.txt or (< filenames.txt) found on internet
I'm sure it's a very dumb thing, but I can't find why I can't iterate more than one line :(
Thanks
It is because your ./log.sh $(cat filenames.txt) is being treated as one argument.
while IFS= read -r line; do
echo "$line";
done < filenames.txt
Edit according to: https://mywiki.wooledge.org/DontReadLinesWithFor
Edit#2:
To preserve leading and trailing whitespace in the result, set IFS to the null string.
You could simplify more and skip using explicit variable and use the default $REPLY
Source: http://wiki.bash-hackers.org/commands/builtin/read
You need to quote the command substitution. Otherwise $1 will just be the first word in the file.
./log.sh "$(cat filenames.txt)"
You should also quote the variable in the script, otherwise all the newlines will be converted to spaces.
echo "$1" >> log_out.txt
If you want to process each word separately, you can leave out the quotes
./log.sh $(cat filenames.txt)
and then use a loop in the script:
#!/bin/bash
for word in "$#"
do
echo "$word"
done >> log_out.txt
Note that this solution only works correctly when the file has one word per line and there are no wildcards in the words. See mywiki.wooledge.org/DontReadLinesWithFor for why this doesn't generalize to more complex lines.
You can iterate with each line.
#!/bin/bash
for i in $*
do
echo $i >> log_out.txt
done

Read command line arguments with input redirection operator in bash

I need to read command line arguments. First arg is script name. second one is redirection operator i.e. "<" and third one is input filename. When I tried to use "$#", I got 0. When I used "$*", it gave me nothing. I have to use "<" this operator. My input file consists of all user input data. If I don't use the operator, It asks user for the input. Can someone please help me? Thank you !
Command Line :
./script_name < input_file
Script:
echo "$*" # gave nothing
echo "$#" # gave me 0
I need to read input filename and store it to some variable. Then I have to change the extension of it. Any help/suggestions should be appreciated.
When a user runs:
./script_name <input_file
...that's exactly equivalent to if they did the following:
(exec <input_file; exec ./script_name)
...first redirecting stdin from input_file, then invoking the script named ./script_name without any arguments.
There are operating-system-specific interfaces you can use to get the filename associated with a handle (when it has one), but to use one of these would make your script only able to run on an operating system providing that interface; it's not worth it.
# very, very linux-specific, won't work for "cat foo | ./yourscript", generally evil
if filename=$(readlink /proc/self/fd/0) && [[ -e $filename ]]; then
set -- "$#" "$filename" # append filename to the end of the argument list
fi
If you want to avoid prompting for input when an argument is given, and to have the filename of that argument, then don't take it on stdin but as an argument, and do the redirection yourself within the script:
#!/bin/bash
if [[ $1 ]]; then
exec <"$1" # this redirects your stdin to come from the file
fi
# ...put other logic here...
...and have users invoke your script as:
./script_name input_file
Just as ./yourscript <filename runs yourscript with the contents of filename on its standard input, a script invoked with ./yourscript filename which invokes exec <"$1" will have the contents of filename on its stdin after executing that command.
< is used for input redirection. And whatever is at the right side of < is NOT a command line argument.
So, when you do ./script_name < input_file , there will be zero (0) command line arguments passed to the script, hence $# will be zero.
For your puprpose you need to call your script as:
./script_name input_file
And in your script you can change the extension with something like:
mv -- "$1" "${1}_new_extension"
Edit: This was not what OP wanted to do.
Altough, there is already another spot on answer, I will write this for the sake of completeness. If you have to use the '<' redirection you can do something like this in your script.
while read filename; do
mv -- "$filename" "${filename}_bak"
done
And call the script as, ./script < input_file. However, note that you will not be able to take inputs from stdin in this case.
Unfortunately, if you're hoping to take redirection operators as arguments to your script, you're not going to be able to do that without surrounding your command line arguments in quotes:
./script_name "<input_file"
The reason for this is that the shell (at least bash or zsh) processes the command before ever invoking your script. When the shell interprets your command, it reads:
[shell command (./script_name)][shell input redirection (<input_file)]
invoking your script with quotes effectively results in:
[shell command (./script_name)][script argument ("<input_file")]
Sorry this is a few years late; hopefully someone will find this useful.

Bash: ssh input to while loop

I'm trying to query the contents of a log file on a remote server, however, I can't seem to get it to work.
#!/bin/bash
while read line; do
echo "Do stuff to the file, line by line"
done < ( ssh -n user#server "cat /path/to/file" )
I get a syntax error at the first parenthesis. If I remove the parenthesis, I get a syntax error at the "-n" flag.
Everything works properly from the shell, so I'm assuming there is some behavior here that I'm not understanding correctly.
Thanks!
You need another < for the process substitution.
#!/bin/bash
while read line; do
echo "Do stuff to the file, line by line"
done < <( ssh -n user#server "cat /path/to/file" )
The first < specifies the input redirection. The <(...) construct is the process substitution, which is similar to command substitution, but treated the output of the enclosed command as a file, rather than a string.

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.

Resources