Question

SO I am making a program that tests the average REad/Write speed of the hard drive using the dd command and my code is as follows(bash):

a=1
b=1
numval=3
for i in `seq 1 3`;
do
        TEST$i=$(dd if=/dev/zero of=speedtest bs=1M count=100 conv=fdatasync)
        #I think that this is the problem line
done
RESULT=$(($TEST1 + $TEST2))
RESULT=$(($RESULT + $TEST3))
RESULT=$(($RESULT / $numval))
echo $RESULT > Result

The code above returns the following errors (in between the dd outputs): TEST1=: command not found TEST2=: command not found TEST3=: command not found

Please help (believe it or not) this is for a school project edit: I understand that my variable does not have a valid name. but Im wondering if there is a way to do this without this shit: "^$({$-%})$" REGEX? is there way to do it without that?

Was it helpful?

Solution

You have (at least) two problems.

  1. TEST$i=... is not valid bash syntax for a variable assignment. And if the first "word" in a command line is not a valid assignment, then it's treated as a command name. So bash goes ahead and substitutes the value of $i for $i and the output of the dd command for $(dd ...) (see below), ending up with the successive "commands" TEST1=, TEST2= and TEST3=. Those aren't known commands, so it complains.

    In an assignment, the only characters you can put before the = are letters, numbers and _ (unless it is an array assignment), which means that you cannot use parameter substitution to create a variable name. (But you could use an array.)

  2. You seem to be assuming that the dd command will output the amount of time it took, or something like that. It doesn't. In fact, it doesn't output anything on stdout. It will output several lines on stderr, but stderr isn't captured with $(...)

OTHER TIPS

First problem: you can't use a variable name that's defined in terms of other variables (TEST$i=...) without jumping through some special hops. There are several ways around this. You could use the declare command (declare TEST$i=...), or use an array (TEST[i]=... and then e.g. RESULT=$((TEST[1] + TEST[2]))), or what I'd recommend: accumulate the times as you go without bothering with the numbered TEST1 etc variables:

numval=3

result=0
for i in `seq 1 $numval`; do
    test=$(dd if=/dev/zero of=speedtest bs=1M count=100 conv=fdatasync)
    result=$((result + test))
done

result=$((result / numval))

(Note that I prefer to use lowercase variable names in shell scripts, to avoid accidentally using one of the shell's predefined variables and making a mess. Also, inside $(( )), variables are automatically replaced, so you don't need $ there.)

However, this still won't work because...

Second problem: dd doesn't output a number. In fact, it doesn't output anything to standard output (which $( ) captures). What it does is output a bunch of numbers and other such things to standard error. Your version of dd is a bit different from mine, but its stderr output is probably something like this:

$ dd if=/dev/zero of=/dev/null bs=1m count=100
100+0 records in
100+0 records out
104857600 bytes transferred in 0.011789 secs (8894645697 bytes/sec)

... and you presumably want to pick out the bytes/sec figure. Depending on your dd's exact output, something like this might work:

$ dd if=/dev/zero of=/dev/null bs=1m count=100 2>&1 | sed -n 's/^.*(\([0-9]*\) bytes.*$/\1/p'
8746239457

What this does is redirect dd's error output to standard output (2>&1), then pipe (|) that to a somewhat messy sed command that looks for something matching "(", then a bunch of digits, then " bytes", and outputs just the digits part.

Here's the full script I wind up with:

#!/bin/bash
numval=3

result=0
for i in `seq 1 $numval`; do
    test=$(dd if=/dev/zero of=speedtest bs=1M count=100 conv=fdatasync 2>&1 | sed -n 's/^.*(\([0-9]*\) bytes.*$/\1/p')
    result=$((result + test))
done

result=$((result / numval))
echo "$result" >Result
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top