I'm trying to put together what should be a fairly simple shell script. The script will have a parameter that is a decimal value. The script will need to convert the decimal to a hex value and then combine that value with a command to be run.
For example if I run the command
./myscript.sh 45
It would need to convert 45 to hex (2d) and then append 2d to the end of a string.
You have the function print that might be useful.
printf "%x" 45
2d
It works similarly to high level programming languages print function/call where it can transform a value using % formatting. Here, %x, formats the first given positional argument to its hex value.
Then you could utilize this by assigning the value to a variable and take it from there.
x=$(printf "%x" 45)
Also, the first argument to a script lives in $1.
Related
I'm trying to concatenate a series of files, with paddings of zeros so that each of them take up multiples of 512 byte, and forms a floppy image using dd.
I imagined the makefile would look like this:
CurrentLocation := 1
build:
dd if=bootloader of=result bs=1474560 count=1 conv=sync # writes sector 0, guaranteed to be 512B
dd if=file1 of=result obs=512 conv=sync,notrunc # writes file1
# get file size using 'stat' command and convert to sectors
dd if=file2 of=result obs=512 conv=sync,notrunc seek=#CurrentLocation+filesize
... and so on ...
But I couldn't figure out how to do it (or is there a better way to do it).
I'm not sure exactly what numerics you are intending to do. However, I think this example will provide you with what you need:
SHELL = /usr/bin/env bash
CurrentLocation := 1
build:
((sum = $(CurrentLocation) + 2)); echo $$sum
Here I make use of the ((...)) bash syntax, which can handle integer arithmetic. Also note how I escape the Make variable CurrentLocation and the bash variable sum differently. Also note that I define and reference sum in the same line. This is important, as Make runs each line in a different subshell, meaning that sum is not defined on the next line. Finally, I found that I had to explicitly tell Make to use the bash shell using e.g. SHELL = /usr/bin/env bash.
Note that you cannot update the Make variable CurrentLocation using this technique.
I need to plot multiple files in one plot with a bash script. I can manage this by stating the location of the files manually in plot function ie
plot "$outputdir/10/values.csv" using 1:2 with lines title "10", \
"$outputdir/20/values.csv" using 1:2 with lines title "20", \
"$outputdir/30/values.csv" using 1:2 with lines title "30"
This will print all three values files in one plot. But I have a dynamic array which changes depending on how many values it is suppose to get. So lets say the array arr looks like this instead
arr=(10 20 30 40)
#Sometimes arr has more values ex; arr=(10 20 30 40 50 60 70 80)
#arr corresponds to folders that will be created
#And will contain a values.csv file that I want to plot
Then the 40/values.csv will not be printed if I don't manually change the plot code
My current attempt to fix this is a for loop in gnuplot
#The array arr is dynamically generated in another function
#Varies in size as example above
ArrLength=${#arr[#]} #Get the length of array
plot for [p=0:$ArrLength] "$outputdir/${arr[$p]}/values.csv" using 1:2 with lines title "${arr[$p]}"
I don't get a error when plotting but in plot only one value is plotted and thats the first value in array ie it will only plot $outputdir/10/values.csv.
I tried setting p=0:4 to plot the first five files and still it only plotted the first file only. What is wrong with the for loop above?
Best regards
You seem to be mixing bash and gnuplot in a strange way. Using a bash script to try to generate a gnuplot script on the fly with inserted variables is a quick way to confuse yourself. It also makes it difficult to read and debug. It is easy to forget what bash is evaluating and what gnuplot is evaluating.
When I look at your first line
ArrLength=${#arr[#]} #Get the length of array
I can see that this is bash code because gnuplot would interpret a comment beginning with the first #. (This is also bash's syntax for arrays, not gnuplot's.) The dollar sign $ has a different meaning in gnuplot. Rather than mark a variable identifier, $ is a column number operator ($2 is column 2, $i is column i, etc.). So look at the line
plot for [p=0:$ArrLength] "$outputdir/${arr[$p]}/values.csv" using 1:2 with lines title "${arr[$p]}"
This is clearly a line of bash syntax, apparently inside a string trying to write a line of gnuplot. Bash will evaluate the variables $ArrLength, $outputdir, and ${arr[$p]}, and replace them with some string of their values. Also keep in mind that p is a variable in gnuplot, not a variable in bash. Bash will evaluate $p to something (an empty string if it has not been defined). You can't expect the gnuplot variable p to be used as the index in the bash evaluation of ${arr[$p]}, and then somehow result in a different string for each iteration of gnuplot's loop.
In short, what you have written is not gnuplot syntax, and it is really not a minimal and complete bash script either. It is not clear exactly how you intended bash and gnuplot to fit together like this, but it seems you have joined them too tightly.
My suggestion is to write a bash script and write a gnuplot script (as separate files). Gnuplot has its own flow control, iteration loops, and variable evaluation. You can write a self-contained gnuplot script for the general case of everything you need it to do, and then give it specifics on the command line from your bash script.
For example, it seems that your subdirectories are all multiples of 10, and always starting with 10. The only variable aspect is how many there are (what the last one is). Let's say this last value was somehow stored in a gnuplot variable last. Also, suppose we also somehow have the base output directory in outputdir:
(Inside the gnuplot script, named plot.gp):
plot for [p=10:last:10] sprintf("%s/%d/values.csv", outputdir, p) with lines title sprintf("%d", p)
The for [p=10:last:10] means to iterate from 10 through last (inclusive), adding 10 at each iteration. The first sprintf() function (like C) builds a string with the outputdir and p (both are variables in gnuplot). The using 1:2 is not necessary as the first two columns are the default columns to use with lines, but you can include them if you want to be explicit. The second sprintf() builds a title string from the iteration variable p.
Now, this assumes that outputdir and last have meaningful values. You can assign these values from your bash script when you invoke gnuplot on the command line:
(Inside the bash script, invoke the gnuplot script)
gnuplot -e "last=40" -e "outputdir=\"$outputdir\"" plot.gp
The -e option tells gnuplot to evaluate the given string before running the script in the file plot.gp. In this example, the gnuplot variable last will have the value 40 and the gnuplot variable outputdir will have whatever value bash evaluates $outputdir to be. Notice the escaped double quotes inside double quotes. The outer double quotes are to allow bash to evaluate variables inside the string ($outputdir needs to be evaluated by bash). The inner (escaped) quotes are to delimit the string within the gnuplot code. For example, if bash evaluates $outputdir to data, then gnuplot would see outputdir="data" which is a valid gnuplot assignment of a string to the variable outputdir. You could, if you want, combine these two -e options into one:
gnuplot -e "last=40;outputdir=\"$outputdir\"" plot.gp
You will likely want to use the value for last from your array in bash, rather than hard coding it like this. So in practice it may look more like
gnuplot -e "last=${arr[${#arr[#]}-1]};outputdir=\"$outputdir\"" plot.gp
Or, if you have bash 4.3 or later, you should be able to use a negative index:
gnuplot -e "last=${arr[-1]};outputdir=\"$outputdir\"" plot.gp
Notice that there are no escaped quotes around the use of the array variable. It is expected that it will evaluate to an integer (40, 90, etc.) and we want to assign last to an integer, not a string like outputdir.
If this one string seems complex, try thinking about the entire script like this. It would be easy to get confused as to what bash is doing and what gnuplot is doing.
In summary, write a bash script, and a separate gnuplot script. Gnuplot is capable of handling a general case. From bash, just give it some specifics on the fly, don't try to generate the entire script on the fly. It really does make things simpler.
I'm running a C program from a Bash script, and running it through a command called time, which outputs some time statistics for the running of the algorithm.
If I were to perform the command
time $ALGORITHM $VALUE $FILENAME
It produces the output:
real 0m0.435s
user 0m0.430s
sys 0m0.003s
The values depending on the running of the algorithm
However, what I would like to be able to do is to take the 0.435 and assign it to a variable.
I've read into awk a bit, enough to know that if I pipe the above command into awk, I should be able to grab the 0.435 and place it in a variable. But how do I do that?
Many thanks
You must be careful: there's the Bash builtin time and there's the external command time, usually located in /usr/bin/time (type type -a time to have all the available times on your system).
If your shell is Bash, when you issue
time stuff
you're calling the builtin time. You can't directly catch the output of time without some minor trickery. This is because time doesn't want to interfere with possible redirections or pipes you'll perform, and that's a good thing.
To get time output on standard out, you need:
{ time stuff; } 2>&1
(grouping and redirection).
Now, about parsing the output: parsing the output of a command is usually a bad idea, especially when it's possible to do without. Fortunately, Bash's time command accepts a format string. From the manual:
TIMEFORMAT
The value of this parameter is used as a format string specifying how the timing information for pipelines prefixed with the time reserved word should be displayed. The % character introduces an escape sequence that is expanded to a time value or other information. The escape sequences and their meanings are as follows; the braces denote optional portions.
%%
A literal `%`.
%[p][l]R
The elapsed time in seconds.
%[p][l]U
The number of CPU seconds spent in user mode.
%[p][l]S
The number of CPU seconds spent in system mode.
%P
The CPU percentage, computed as (%U + %S) / %R.
The optional p is a digit specifying the precision, the number of fractional digits after a decimal point. A value of 0 causes no decimal point or fraction to be output. At most three places after the decimal point may be specified; values of p greater than 3 are changed to 3. If p is not specified, the value 3 is used.
The optional l specifies a longer format, including minutes, of the form MMmSS.FFs. The value of p determines whether or not the fraction is included.
If this variable is not set, Bash acts as if it had the value
$'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
If the value is null, no timing information is displayed. A trailing newline is added when the format string is displayed.
So, to fully achieve what you want:
var=$(TIMEFORMAT='%R'; { time $ALGORITHM $VALUE $FILENAME; } 2>&1)
As #glennjackman points out, if your command sends any messages to standard output and standard error, you must take care of that too. For that, some extra plumbing is necessary:
exec 3>&1 4>&2
var=$(TIMEFORMAT='%R'; { time $ALGORITHM $VALUE $FILENAME 1>&3 2>&4; } 2>&1)
exec 3>&- 4>&-
Source: BashFAQ032 on the wonderful Greg's wiki.
You could try the below awk command which uses split function to split the input based on digit m or last s.
$ foo=$(awk '/^real/{split($2,a,"[0-9]m|s$"); print a[2]}' file)
$ echo "$foo"
0.435
You can use this awk:
var=$(awk '$1=="real"{gsub(/^[0-9]+[hms]|[hms]$/, "", $2); print $2}' file)
echo "$var"
0.435
I am trying to write a shell script in which I want to assign certain characters to some variables. But since I want to cycle through a set of characters, I can't just write char1='A'. I want to be able to assign 'A' to the variable char1 using its ASCII cose number. I am also very new to shell scripting, so, if a detailed explanation of the command I need to use would be much appreciated. Thanks!
The following command will assign 65 (ASCII value of "A") to the variable char1:
char1=$(printf "%d" "'A")
The command printf is similar to the C function printf() which is used to print preformatted text. Using a quote(') before a character prints the ASCII equivalent of the character.
The following script is showing me "unexpected end of file" error. I have no clue why am I facing this error. My all the quotes are closed properly.
#!/usr/bin/sh
insertsql(){
#sqlite3 /mnt/rd/stats_flow_db.sqlite <<EOF
echo "insert into flow values($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18)"
#.quit
}
for i in {1..100}
do
src_ip = "10.1.2."+$i
echo $src_ip
src_ip_octets = ${src_ip//,/}
src_ip_int = $src_ip_octets[0]*1<<24+$src_ip_octets[1]*1<<16+$src_ip_octets[2]*1<<8+$src_ip_octets[3]
dst_ip = "10.1.1."+$i
dst_ip_octets = ${dst_ip//,/}
dst_ip_int = $dst_ip_octets[0]*1<<24+$dst_ip_octets[1]*1<<16+$dst_ip_octets[2]*1<<8+$dst_ip_octets[3]
insertsql(1, 10000, $dst_ip, 20000, $src_ip, "2012-08-02,12:30:25.0","2012-08-02,12:45:25.0",0,0,0,"flow_a010105_a010104_47173_5005_1_50183d19.rrd",0,12,$src_ip_int,$dst_ip_int,3,50000000,80000000)
done
That error is caused by <<. When encountering that, the script tries to read until it finds a line which has exactly (starting in the first column) what is found after the <<. As that is never found, the script searches to the end and then complains that the file ended unexpectedly.
That will not be your only problem, however. I see at least the following other problems:
You can only use $1 to $9 for positional parameters. If you want to go beyond that, the use of the shift command is required or, if your version of the shell supports it, use braces around the variable name; e.g. ${10}, ${11}...
Variable assignments must not have whitespace arount the equal sign
To call your insertsql you must not use ( and ); you'd define a new function that way.
The cass to your insertsql function must pass the parameters whitespace separated, not comma separated.
A couple of problems:
There should be no space between equal sign and two sides of an assignment: e.g.,: dst_ip="10.1.1.$i"
String concatenation is not done using plus sign e.g., dst_ip="10.1.1.$i"
There is no shift operator in bash, no <<: $dst_ip_octets[0]*1<<24 can be done with expr $dst_ip_octets[0] * 16777216 `
Functions are called just like shell scripts, arguments are separated by space and no parenthesis: insertsql 1 10000 ...
That is because you don't follow shell syntax.
To ser variable you are not allowed to use space around = and to concatenate two parts of string you shouldn't use +. So the string
src_ip = "10.1.2."+$i
become
src_ip="10.1.2.$i"
Why you're using the string
src_ip_octets = ${src_ip//,/}
I don't know. There is absolutely no commas in you variable. So even to delete all commas it should look like (the last / is not required in case you're just deleting symbols):
src_ip_octets=${src_ip//,}
The next string has a lot of symbols that shell intepreter at its own way and that's why you get the error about unexpected end of file (especially due to heredoc <<)
src_ip_int = $src_ip_octets[0]*1<<24+$src_ip_octets[1]*1<<16+$src_ip_octets[2]*1<<8+$src_ip_octets[3]
So I don't know what exactly did you mean, though it seems to me it should be something like
src_ip_int=$(( ${src_ip_octets%%*.}+$(echo $src_ip_octets|sed 's/[0-9]\+\.\(\[0-9]\+\)\..*/\1/')+$(echo $src_ip_octets|sed 's/\([0-9]\+\.\)\{2\}\(\[0-9]\+\)\..*/\1/')+${src_ip_octets##*.} ))
The same stuff is with the next strings.
You can't do this:
dst_ip_int = $dst_ip_octets[0]*1<<24+$dst_ip_octets[1]*1<<16+$dst_ip_octets[2]*1<<8+$dst_ip_octets[3]
The shell doesn't do math. This isn't C. If you want to do this sort of calculation, you'll need to use something like bc, dc or some other tool that can do the sort of math you're attempting here.
Most of those operators are actually shell metacharacters that mean something entirely different. For example, << is input redirection, and [ and ] are used for filename globbing.