I am encountering a very weird situation when wrapping a bash script call in echo $(). This is strange enough that I don't know what code to present, so I will describe the general situation. I have a script, which we will call "run.sh", and it has some output. This is generally formatted quite nicely, with whitespace and line breaks.
I am trying to compare this output with a value that I got when I ran it once previously. To do this, the code compares the "new" value with the old by checking if these two are the same, i.e.:
expression=$(./runProcess.sh "$process");
expected=$(cat UnitTests/expect-process-$process);
if [ "$expression" == "$expected" ]; then
Clearly to get a value of "old" to compare with future testings I need to compute $(./runProcess.sh) by hand. When I do this, I get a version of the output with significantly less whitespace. However it is clearly wrong, because the contents of ls turn up in the middle of it. By that I mean that I get the following type of output running these two commands:
./runProcess.sh g,g:
R2With2Gluons =
+ ncol*i_*pi_^2*A*g^2 * (
- 17/24*d_(mu1,mu2)*d_(m1,m2)*p1.p1
- 31/8*d_(mu1,mu2)*d_(m1,m2)*p1.p2
- 17/24*d_(mu1,mu2)*d_(m1,m2)*p2.p2
+ 7/12*d_(m1,m2)*p1(mu1)*p1(mu2)
+ 1/24*d_(m1,m2)*p1(mu1)*p2(mu2)
+ 89/24*d_(m1,m2)*p1(mu2)*p2(mu1)
+ 7/12*d_(m1,m2)*p2(mu1)*p2(mu2)
);
0.01 sec out of 0.01 sec
echo $(./runProcess.sh g,g):
R2With3Gluons = + coeff(m1,m2,m3)*ncol*pi_^2*A*g^3 Auto Diagrams UnitTests colourCalc.frm form.set functions.frm output.frm process.frm process.mid qgraf2form.frm qgrafProcessor.py runProcess.sh runProcesses.sh test vertices.frm ( + 35/24*d_(mu1,mu2)*p1(mu3) - 35/24*d_(mu1,mu2)*p2(mu3) - 35/24*d_(mu1,mu3)*p1(mu2) + 35/24*d_(mu1,mu3)*p3(mu2) + 35/24*d_(mu2,mu3)*p2(mu1) - 35/24*d_(mu2,mu3)*p3(mu1) ); 0.40 sec out of 0.40 sec
And here is ls:
ls:
Auto form.set process.mid runProcesses.sh
Diagrams functions.frm qgraf2form.frm test
UnitTests output.frm qgrafProcessor.py vertices.frm
colourCalc.frm process.frm runProcess.sh
I can provide exact examples if necessary, but I hope this is illuminating enough. Why could this possibly be happening? I'm using bash on OS X Mountain Lion.
Use more quotes!!!
Try:
echo "$(./run.sh)"
instead. (Yes, with quotes).
Try:
old=$(./run.sh)
echo "$old"
you'll have the correct output (with $old in quotes). Now, regarding your test, use, as advised by sampson-chen:
[[ "$old" == "$(./run.sh)" ]]
(you don't need to quote the variables or the command substitution when assigning the variable old, but, as a general rule, you can use quotes every time). ((see Gordon Davisson's excellent comments to this post, that I've actually upvoted, with a few caveats about globs and quoting variables inside [[ ... ]])).
Edit. As you've edited your post, I see you're using an inefficient cat. Instead of:
expected=$(cat UnitTests/expect-process-$process)
please use
expected=$(< "UnitTests/expect-process-$process")
It's hard to say without your exact script, but for starters, your comparison:
old == $(./run.sh);
should be:
if [[ "$old" == "$(./run.sh)" ]]; then
Related
I have a script like this:
#!/bin/bash
x=${1:-20}
for ((i=1;i<=x;i++))
{
if ((i%3==0))
{
echo 'Fizz'
}
echo $i
}
I get an error color on the last brace in VIM and when I try to run the script I get a "syntax error near unexpected token" for that same brace. Without the nested if statement, this will print 1 through 20 on a new line for each number, which is the expected outcome. If the number is divisible by 3, it should print Fizz instead of that number. I'm not as worried about how to implement the replacement, that should be easy to figure out, but what I don't understand is why I cannot use a brace to close the for loop. If I take out the brace, I get an error that says end of file expected. So what is the proper syntax for ending a for loop with a nested if statement? I've looked around online and here on stack but haven't found a similar format to what I am trying to do. I don't like the
for f in *
format as it is not as easy to read for someone coming from a different coding language and I like to keep my code looking very similar across different languages (I use comments too, but just the same, I try to keep things as similar as possible which is why I used (( )) with the for loop.)
If I comment out the if statement and leave everything else intact, the error disappears and it will print
1
Fizz
2
Fizz
etc.
Any insight into this would be greatly appreciated. Thanks!
So here is what I was able to figure out thanks to #Cyrus:
x=${1:-20}
for ((i=1;i<=x;i++))
do
if ((i%3==0))
then
echo 'Fizz'
else
echo $i
fi
done
In many ways bash is simpler than most other languages but that makes it harder to work with when you are used to "higher" level languages.
So, to help out anyone else that's like me and just starting to code with bash, here is the full program I made, with comments as to why I coded it the way I did. If there are errors in my explanation or my formatting style, please point them out. Thanks! This was kind of fun to write, call me crazy.
# This will literally just print the string inside the single quotes on the screen
echo 'Try running this again but with something like this: fizzbuzz 25 pot kettle black'
# The $0 is the first index, in other words the file name of the executable,
# this will set the default value of x to 20 but will allow the user to input
# something else if they want.
x=${1:-20}
# This is the same but with string variables
f=${2:-FizzBuzz}
g=${3:-Fizz}
b=${4:-Buzz}
# We start the index variable at 1 because it's based on the input,
# otherwise it would echo 0 thru 19
for ((i=1;i<=x;1++))
do
# I recommend using (( )) for if statement arithmetic operations
# since the syntax is similar to other programming languages
if ((i%3==0 && i%5==0)); then echo $f
# you need to use a semicolon to separate if and then if they are
# on the same line, otherwise you can just go to the next line for
# your then statement
else if ((i%3==0)); then echo $g
else if ((i%5==0)); then echo $b
else echo $1
# You need fi in order to finish an if then statement
fi fi fi
done
I am trying to have
I have two registers reg_a and reg_b, each are 32 bit. reg_a is used to store the epoch time (unix time), so it can go upto a maximum of 2^32 -1. If an overflow occurs, the overflow should be stored in reg_b. I also want to write them in a file rom.txt . I am trying to do this in a Makefile. This is how far I got (It is more of a pseudocode, there are syntax errors). Would be happy to know if there is a better way to do this.
# should be 2^32-1, but lets consider the below for example
EPOCH_MAX = 1500000000
identifier:
# get the epoch value
epoch=$(shell date +%s)
# initialize the reg_a, reg_b, assuming that overflow has not occurred
reg_a=$(epoch)
reg_b=0
# if overflow occurs
if [ ${epoch} -gt $(EPOCH_MAX)] ; then \
reg_a=$(EPOCH_MAX) ;\
reg_b=$(shell $(epoch)\-$(EPOCH_MAX) | bc) ;\
fi ;\
# here I want to print the values in a text file
echo $$(reg_a) > rom.txt
echo $$(reg_b) >> rom.txt
I am novice to Makefiles. The above is just a sort of pseudocode which tells what I want to do (Mostly through reading some webpages). I will be happy if someone can help me with the above. Thanks.
you've been asking a lot of questions about make. I think you might benefit from spending some time reading the GNU make manual
Pertinent to your question, each logical line in a recipe is run in a separate shell. So, you cannot set a shell variable in one logical line, then use the results in another one.
A "logical line" is all the physical lines where the previous one ends in backslash/newline.
So:
identifier:
# get the epoch value
epoch=$(shell date +%s)
# initialize the reg_a, reg_b, assuming that overflow has not occurred
reg_a=$(epoch)
reg_b=0
Will run 5 separate shells, one for each line (including the comments! Every line indented with a TAB character is considered a recipe line, even ones that begin with comments).
On the other hand, this:
if [ ${epoch} -gt $(EPOCH_MAX)] ; then \
reg_a=$(EPOCH_MAX) ;\
reg_b=$(shell $(epoch)\-$(EPOCH_MAX) | bc) ;\
fi ;\
Runs the entire if-statement in a single shell, because the backslash/newline pairs create a single logical line.
Second, you have to keep very clear in your mind the difference between make variables and shell variables. In the above the line epoch=$(shell date +%s) is setting the shell variable epoch (which value is immediately lost again when the shell exits).
The line reg_a=$(epoch) is referencing the make variable epoch, which is not set and so is empty.
I'm writing a quick shell script to build and execute my programs in one fell swoop.
I've gotten that part down, but I'd like to include a little if/else to catch bad extensions - if it's not an .adb (it's an Ada script), it won't let the rest of the program execute.
My two-part question is:
How do I grab just the extension? Or is it easier to just say *.adb?
What would the if/else statement look like? I have limited experience in Bash so I understand that's a pretty bad question.
Thanks!
There are ways to extract the extension, but you don't really need to:
if [[ $filename == *.adb ]] ; then
. . . # this code is run if $filename ends in .adb
else
. . . # this code is run otherwise
fi
(The trouble with extracting the extension is that you'd have to define what you mean by "extension". What is the extension of a file named foo? How about a file named report.2012.01.29? So general-purpose extension-extracting code is tricky, and not worth it if your goal is just to confirm that file has a specific extension.)
There are multiple ways to do it. Which is best depends in part on what the subsequent operations will be.
Given a variable $file, you might want to test what the extension is. In that case, you probably do best with:
extn=${file##*.}
This deletes everything up to the last dot in the name, slashes and all, leaving you with adb if the file name was adafile.adb.
If, on the other hand, you want to do different things depending on the extension, you might use:
case "$file" in
(*.adb) ...do things with .adb files;;
(*.pqr) ...do things with .pqr files;;
(*) ...cover the rest - maybe an error;;
esac
If you want the name without the extension, you can do things the more traditional way with:
base=$(basename $file .adb)
path=$(dirname $file)
The basename command gives you the last component of the file name with the extension .adb stripped off. The dirname command gives you the path leading to the last component of the file name, defaulting to . (the current directory) if there is no specified path.
The more recent way to do those last two operations is:
base=${file##/}
path=${file%/*}
The advantage of these is that they are built-in operations that do not invoke a separate executable, so they are quicker. The disadvantage of the built-ins is that if you have a name that ends with a slash, the built-in treats it as significant but the command does not (and the command is probably giving you the more desirable behaviour, unless you want to argue GIGO).
There are other techniques available too. The expr command is an old, rather heavy-weight mechanism that would not normally be used (but it is very standard). There may be other techniques using the (( ... )), $(( ... )) and [[ ... ]] operators to evaluate various sorts of expression.
To get just the extension from the file path and name, use parameter expansion:
${filename##*.} # deletes everything to the last dot
To compare it with the string adb, just do
if [[ ${filename##*.} != adb ]] ; then
echo Invalid extension at "$filename".
exit 1
fi
or, using 'else`:
if [[ ${filename##*.} != adb ]] ; then
echo Invalid extension at "$filename".
else
# Run the script...
fi
Extension:
fileext=`echo $filename | sed 's_.*\.__'`
Test
if [[ x"${fileext}" = "xadb" ]] ; then
#do something
fi
I have a small loop problem as below.
I have 3 different values to be used while running the same script -
va1="some-value1"
va2="some-value2"
va3="some-value3"
Now I want to use these three variable values to be used for running the same command, like -
while (i=0,i<=3,i++)
do
bin/java -s (run something with $var);
done
Now I want $var taking the value of var1, var2 and var3 each time it runs,
so can someone please tell me how do we achieve the above?
I tried doing -
for $1 $2 $3
do
case 1
case 2
case 3
done
OR
while read a b c
do
<code assumed to have loop iteration>
done <<< $(command)
But it isnt working as expected... Would really appreciate your help on this.
Thanks,
Brian
You forgot the 'in' part of the syntax:
for var in $va1 $va2 $va3
do
command $var
done
try
while ((i=0,i<=3,i++))
do
eval bin/java -s \$var$i
done
This is a great example of how to use eval. Note that 1. the value of $i is seen by the shell has it scans the line. Then because $var is escaped like \$var, it is not
'make visible' in the first scan. 2. Eval forces a 2nd scan of the cmd-line, so it sees $var1 or whatever, and that value is substituted into the command-line for execution.
I hope this helps.
P.S. Welcome to StackOverflow and let me remind you of three things we usually do here: 1) As you receive help, try to give it too, answering questions in your area of expertise 2) Read the FAQs, http://tinyurl.com/2vycnvr , 3) When you see good Q&A, vote them up by using the gray triangles, http://i.imgur.com/kygEP.png , as the credibility of the system is based on the reputation that users gain by sharing their knowledge. Also remember to accept the answer that better solves your problem, if any, by pressing the checkmark sign , http://i.imgur.com/uqJeW.png
You can use indirect variable expansion with ${!...}:
va1="some-value1"
va2="some-value2"
va3="some-value3"
for ((i=1;i<=3;i++)); do
varname="va$i"
echo "$varname = ${!varname}"
done
This prints:
va1 = some-value1
va2 = some-value2
va3 = some-value3
I am trying to read part of a file and stop and a particular line, using bash. I am not very familiar with bash, but I've been reading the manual and various references, and I don't understand why something like the following does not work (but instead produces a syntax error):
while { read -u 4 line } && (test "$line" != "$header_line")
do
echo in loop, line=$line
done
I think I could write a loop that tests a "done" variable, and then do my real tests inside the loop and set "done" appropriately, but I am curious as to 1) why the above does not work, and 2) is there some small correction that would make it work? I still fairly confused about when to use [, (, {, or ((, so perhaps some other combination would work, though I have tried several.
(Note: The "read -u 4 line" works fine when I call it above the loop. I have opened a file on file descriptor 4.)
I think what you want is more like this:
while read -u 4 line && test "$line" != "$header_line"
do
...
done
Braces (the {} characters) are used to separate variables from other parts of a string when whitespace cannot be used. For example, echo "${var}x" will print the value of the variable var followed by an x, but echo "$varx" will print the value of the variable varx.
Brackets (the [] characters) are used as a shortcut for the test program. [ is another name for test, but when test detects that it was called with [ it required a ] as its last argument. The point is clarity.
Parenthesis (the () characters) are used in a number of different situations. They generally start subshells, although not always (I'm not really certain in case #3 here):
Retrieving a single exit code from a series of processes, or a single output stream from a sequence of commands. For example, (echo "Hi" ; echo "Bye") | sed -e "s/Hi/Hello/" will print two lines, "Hello" and "Bye". It is the easiest way to get multiple echo statements to produce a single stream.
Evaluating commands as if they were variables: $(expr 1 + 1) will act like a variable, but will produce the value 2.
Performing math: $((5 * 4 / 3 + 2 % 1)) will evaluate like a variable, but will compute the result of that mathematical expression.
The && operator is a list operator - he seperates two commands and only executes when the first is true, but in this case the first is the while and he is expecting his do stuff. And then he reaches do and the while stuff is already history.
Your intention is to put it into the expression. So you put it together with (). E.g. this a solution with just a small change
while ( read -u 4 line && test "$line" != "$header_line" )