bash how to input * from a file - bash

The problem:
I have a simple compiler that compiles simple RPN expression. The compiler is invoked like this:
./compiler 1 2 3 + "*"
This works fine.
Now, let's say I've put
1 2 3 + "*"
into a file called input. Then when I invoke the compiler like this:
./compiler $(cat input)
My compiler will complain: unknown symbol: "*"
If I remove the double quote around *, the * gets expanded to file names. I've also tried '' and ``, no good.
So, how can I input a normal * from a file?

zoo.sh with content
#!/bin/bash
set -f
echo $#
m.txt with content:
1 2 3 + *
In a shell do:
set -f
./zoo.sh 1 2 4 + *
1 2 4 + *
./zoo.sh $(cat m.txt)
1 2 3 + *
The shell is doing the expansion for you. This happens before the command runs. If you want it to stop you need to explicitly tell it. Read about it here:
http://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin
Above the script also sets this to prevent the echo inside the script to do the expansion and make this work as I imagine your code works. Your compiler probably does not (need) do this.
Remember to do a set +f to restore the filename expansion.

The only way to remove quoting characters from a variable that contains them:
a='1 2 3 + "*"'
is with shell "quote removal". That is done every time a string is parsed by the shell (if it is not the result of some other expansion). So, this will not remove the quotes, even if un-quoted:
$ echo $a
1 2 3 + "*"
Even repeated, quotes will not be removed:
$ echo $(echo $( echo $a ) )
1 2 3 + "*"
But quotes will be removed if we re-parse the string with eval:
$ eval echo $a
1 2 3 + *
Or if the string is sent to an external program (not a builtin), it will be re-parsed on its return. That is what happens with xargs:
$ xargs <<<$a
1 2 3 + *
Now, for your specific case, either do:
$ eval echo $(<file.csv)
or
$ xargs <file.csv
In this case, xargs is executing a default echo, so is basically doing the same as above.
Please be aware that either commands work with this specific code, but are a source for serious security risks. Remember: eval is evil.

Related

Updating (sequentially adding a value into) a variable within a for loop, in bash script

I am trying to update a variable within a for loop, sequentially adding to the variable, and then printing it to a series of file names.
Actually, I want to sequentially update a series of file, from a tmp file distTmp.RST to sequentially dist1.RST, dist2.RST, etc..
The original distTmp.RST contains a line called "WWW". I want to replace the string with values called 21.5 in dist1.RST, 22.5 in dist2.RST, 23.5 in dist3.RST, etc...
My code is as follows:
#!/bin/bash
wm=1
wM=70
wS=1
F=20.5
for W in {${wm}..${wM}..${wS}}; do
F=$(${F} + 1 | bc)
echo ${F}
sed "s/WWW/"${F}"/g" distTmp.RST > dist${W}.RST
done
echo ${F}
========
But I am getting error message as follows:
change.sh: line 13: 20.5 + 1 | bc: syntax error: invalid arithmetic operator (error token is ".5 + 1 | bc")
Kindly suggest me a solution to the same.
Kindly suggest me a solution to the same.
This might do what you wanted. Using a c-style for loop.
#!/usr/bin/env bash
wm=1
wM=70
wS=1
F=20.5
for ((w=wm;w<=wM;w+=wS)); do
f=$(bc <<< "$F + $w")
echo "$f"
sed "s/WWW/$f/g" distTmp.RST > "dist${w}.RST"
done
The error from your script might be because the order of expansion. brace expansion expansion happens before Variable does.
See Shell Expansion
Use
F=$(echo ${F} + 1 | bc)
instead of F=$((${F} + 1 | bc)). The doubled-up parentheses are what caused your error. Double parentheses weren't in the original code, but I get a different error saying 20.5: command not found with the original code, so I tried doubling the parentheses and get the error in the question. Apparently, floating point numbers aren't supported by $(()) arithmetic evaluation expressions in Bash.

Unix FOR loop not working with IP addressees

The below loop should read 1 line at a time to the veriable m. But it prints some junk value. Please help.
MyMachine:/u/home/Mohammed_Junaid> cat /tmp/F5
[10.222.73.99:22]
[10.000.73.99:22]
[10.111.73.99:22]
MyMachine:/u/home/Mohammed_Junaid>
MyMachine:/u/home/Mohammed_Junaid> for m in $(cat /tmp/F5); do echo $m;done
1
2
1
2
1
2
MyMachine:/u/home/Mohammed_Junaid>
Those strings are also valid glob-patterns. Because you're not quoting the $m variable, you're letting the shell perform filename expansion.
It happens that the string [10.222.73.99:22] is equivalent to the glob pattern [012739.:] which will match a filename of a single one of those characters, and it appears that you have a file named 1 and a file named 2 in your current directory.
Always quote your shell variables, and don't use for to iterate the lines of a file
while IFS= read -r m; do echo "$m"; done < /tmp/F5

How can I pass an unquoted string with spaces as a quoted argument?

Better explained with an example. I am writing a simple wrapper (a function in my .bashrc) around the mail command.
Here is my current function which doesn't work correctly:
function email_me() { echo "$#" | mail -s "\"$#\"" myaddress#email.com; }
Here is my desired usage - this would send an email with both the subject and body set to testing 1 2 3. Note I specifically do not want to have to put quotes in manually.
~$ email_me testing 1 2 3
Thus I want the string replacement to occur like this:
echo "testing 1 2 3" | mail -s "testing 1 2 3" myaddress#email.com
However no matter what I try, it's as though the -s argument doesn't have quotes around it, and email an email with the subject "testingis sent to the following recipients: 1, 2, 3, and myaddress#email.com
How can I make the -s argument consider "testing 1 2 3" to be a single string?
I would suggest using
function email_me() { printf %s\\n "$*" | mail -s "$*" myaddress#email.com; }
"$*" is indeed the special variable containing all arguments together in one string
using printf instead of echo saves you from suprises with -n -e and whatever else your implementation of echo supports.
Still, there will be situations where you'll have to quote the arguments to email_me to avoid globbing and preserve whitespace:
email_me 2 * 2 = 4
[sends you all file names in current directory]
email_me a b
[sends "a b" with only one space]

Unfamiliar shell syntax in ack-grep install script

From the ack installation page (http://betterthangrep.com/install/) there is a one-liner installation with curl:
curl http://betterthangrep.com/ack-standalone > ~/bin/ack && chmod 0755 !#:3
I understand that it's getting the file from the website and saving it to ~/bin/ack, then setting permissions, but what does that last part ( !#:3 ) do ? (I do not recognize the syntax and Googling didn't yield any helpful results)
See the section called HISTORY EXPANSION in man bash, particularly the Word Designators subsection. !#:3 refers to the third word of the pipe, which is (in your example) ~/bin/ack. In order, the words of the command are curl, 0; http://betterthangrep.com/ack-standalone, 1; >, 2; ~/bin/ack, 3; &&, 4; chmod, 5; 0755, 6; !#:3, 7.
That is, !#:3 is a way to repeat the filename without using a separate variable or literal text.
Regarding the question about > and whitespace, note that > is a metacharacter, which man bash defines as a “character that, when unquoted, separates words. One of the following: | & ; ( ) < > space tab”. So whitespace does not affect whether > counts as a token. But note that in the following example, the first 3 is quoted so that bash doesn't interpret it as part of a 3> redirection. When the line was entered, bash echoed the expanded line and then executed it.
$ seq '3'>bbb;cat !#:3 !#:2 ccc; head !#:3 !#:8
seq '3'>bbb;cat bbb > ccc; head bbb ccc
==> bbb <==
1
2
3
==> ccc <==
1
2
3
!# means to execute the command typed so far, but you can specify a parameter with :n. :0 would be the first word (curl), :1 the second one (http...) and so on.

bash: calling a scripts with double-quote argument

I have a bash scripts which an argument enclosed with double quotes, which creates a shape-file of map within the given boundries, e.g.
$ export_map "0 0 100 100"
Within the script, there are two select statements:
select ENCODING in UTF8 WIN1252 WIN1255 ISO-8859-8;
...
select NAV_SELECT in Included Excluded;
Naturally, these two statements require the input to enter a number as an input. This can by bypassed by piping the numbers, followed by a newline, to the script.
In order to save time, I would like to have a script that would create 8 maps - for each combination of ENCODING (4 options) and NAV_SELECT (2 options).
I have written another bash script, create_map, to server as a wrapper:
#!/bin/bash
for nav in 1 2 3 4;
do
for enc in 1 2;
do
printf "$nav\n$enc\n" | /bin/bash -c "./export_map.sh \"0 0 100 100\""
done
done
**This works (thanks, Brian!), but I can't find a way to have the numeric argument "0 0 100 100" being passed from outside the outer script. **
Basically, I'm looking for way to accept an argument within double quotes to a wrapper bash script, and pass it - with the double quotes - to an inner script.
CLARIFICATIONS:
export_map is the main script, being called from create_map 8 times.
Any ideas?
Thanks,
Adam
If I understand your problem correctly (which I'm not sure about; see my comment), you should probably add another \n to your printf; printf does not add a trailing newline by default the way that echo does. This will ensure that the second value will be read properly by the select command which I'm assuming appears in export_map.sh.
printf "$nav\n$enc\n" | /bin/bash -c "./export_map.sh \"100 200 300 400\""
Also, I don't think that you need to add the /bin/bash -c and quote marks. The following should be sufficient, unless I'm missing something:
printf "$nav\n$enc\n" | ./export_map.sh "100 200 300 400"
edit Thanks for the clarification. In order to pass an argument from your wrapper script, into the inner script, keeping it as a single argument, you can pass in "$1", where the quotes indicate that you want to keep this grouped as one argument, and $1 is the first parameter to your wrapper script. If you want to pass all parameters from your outer script in to your inner script, each being kept as a single parameter, you can use "$#" instead.
#!/bin/bash
for nav in 1 2 3 4;
do
for enc in 1 2;
do
printf "$nav\n$enc\n" | ./export_map.sh "$1"
done
done
Here's a quick example of how "$#" works. First, inner.bash:
#!/bin/bash
for str in "$#"
do
echo $str
done
outer.bash:
#!/bin/bash
./inner.bash "$#"
And invoking it:
$ ./outer.bash "foo bar" baz "quux zot"
foo bar
baz
quux zot

Resources