0

I am trying to write a task-runner for command line. No rationale. Just wanted to do it. Basically it just runs a command, stores the output in a file (instead of stdout) and meanwhile prints a progress indicator of sorts on stdout and when its all done, prints Completed ($TIME_HERE).

Here's the code:

#!/bin/bash
task() {
  TIMEFORMAT="%E"
  COMMAND=$1
  printf "\033[0;33m${2:-$COMMAND}\033[0m\n"
  while true
  do
    for i in 1 2 3 4 5
    do
      printf '.'
      sleep 0.5
    done
    printf "\b\b\b\b\b     \b\b\b\b\b"
    sleep 0.5
  done &
  WHILE=$!
  EXECTIME=$({ TIMEFORMAT='%E';time $COMMAND >log; } 2>&1)
  kill -9 $WHILE
  echo $EXECTIME
  #printf "\rCompleted (${EXECTIME}s)\n"
}

There are some unnecessarily fancy bits in there I admit. But I went through tons of StackOverflow questions to do different kinds of fancy stuff just to try it out. If it were to be applied anywhere, a lot of fat could be cut off. But it's not.

It is to be called like:

task "ping google.com -c 4" "Pinging google.com 4 times"

What it'll do is print Pinging google.com 4 times in yellow color, then on the next line, print a period. Then print another period every .5 seconds. After five periods, start from the beginning of the same line and repeat this until the command is complete. Then it's supposed to print Complete ($TIME_HERE) with (obviously) the time it took to execute the command in place of $TIME_HERE. (I've commented that part out, the current version would just print the time).

The Issue

The issue is that that instead of the execution time, something very weird gets printed. It's probably something stupid I'm doing. But I don't know where that problem originates from. Here's the output.

$ sh taskrunner.sh 
Pinging google.com 4 times
..0.00user 0.00system 0:03.51elapsed 0%CPU (0avgtext+0avgdata 996maxresident)k 0inputs+16outputs (0major+338minor)pagefaults 0swaps

Running COMMAND='ping google.com -c 4';EXECTIME=$({ TIMEFORMAT='%E';time $COMMAND >log; } 2>&1);echo $EXECTIME in a terminal works as expected, i.e. prints out the time (3.559s in my case.)

I have checked and /bin/sh is a symlink to dash. (However that shouldn't be a problem because my script runs in /bin/bash as per the shebang on the top.)

I'm looking to learn while solving this issue so a solution with explanation will be cool. T. Hanks. :)

0

1 Answer 1

2

When you invoke a script with:

 sh scriptname

the script is passed to sh (dash in your case), which will ignore the shebang line. (In a shell script, a shebang is a comment, since it starts with a #. That's not a coincidence.)

Shebang lines are only interpreted for commands started as commands, since they are interpreted by the system's command launcher, not by the shell.


By the way, your invocation of time does not correctly separate the output of the time builtin from any output the timed command might sent to stderr. I think you'd be better with:

EXECTIME=$({ TIMEFORMAT=%E; time $COMMAND >log.out 2>log.err; } 2>&1)

but that isn't sufficient. You will continue to run into the standard problems with trying to put commands into string variables, which is that it only works with very simple commands. See the Bash FAQ. Or look at some of these answers:

(Or probably hundreds of other similar answers.)

Sign up to request clarification or add additional context in comments.

7 Comments

So I checked, running it with bash script.sh works fine. Does that mean that problem lies with dash only? Also I understand your second point about redirecting stderr as well. I'll do that. And I read the link you posted, but I don't get it. You mean I shouldn't be putting commands in in $COMMAND or passing as variables to task()? What would be a better way to do something similar?
@ZiaUrRehman: dash doesn't have a builtin time so /usr/bin/time is used, and /usr/bin/time does not use the $TIMEFORMAT environment variable. (It uses $TIME or a command-line argument; see man time.) In general, you should chmod a+x your scripts, and then just run them as ./script.sh. Or even ./script. Or better still, put them in /usr/local/bin or some other directory in your $PATH and then run them as just script.
@ZiaUrRehman: I added some more reading material for the "how do I pass commands through a script" problem. As you will see, I (and many others) prefer the use of arrays to do this.
Great. Thank you for the reading material. Also this might be a little off-topic, but dash vs. bash? I've read some material on it and apparently Ubuntu prefers dash because it starts up faster and Ubuntu has to spawn a lot of shells on boot time. But that still doesn't clear up which terminal should be used for your day-to-day scripting. I guess bash is preferred generally. But if dash has some advantages over bash, it might be worth looking into as the default shebang.
@ZiaUrRehman: For day-to-day scripting, use the scripting language you prefer. For me, bash arrays are essential; I could use ksh or zsh but I'm used to bash. For scripts which run as root, use the most restricted shell you can find. For scripts intended for cross-platform general distribution, use #!/bin/sh and make sure you stick to just Posix-standard constructions, or use a specific but widely-available shell like bash and document the dependency.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.