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. :)

Zia Ur Rehman
  • 1,141
  • 2
  • 11
  • 20

1 Answers1

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.)

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • 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? – Zia Ur Rehman Oct 19 '14 at 19:26
  • @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`. – rici Oct 19 '14 at 20:36
  • @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. – rici Oct 19 '14 at 20:51
  • 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. – Zia Ur Rehman Oct 20 '14 at 19:14
  • @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. – rici Oct 20 '14 at 19:17
  • Also, I read `man time` before hand, and using `time -f "%E"` was my first instinct. But for some reason `time` thinks the command starts at `-f`and sure enough, complains that `-f: command not found`. That was the only reason to switch to the `$TIMEFORMAT` variable. Using options is way cleaner. Also, `echo` does the same. If I do `echo -ne "Hello world\n"` it prints `-ne Hello world` (with the newline at the end). Might be dash issue. – Zia Ur Rehman Oct 20 '14 at 19:18
  • @ZiaUrRehman: `man x` documents the utility `x`. `bash` (and ksh and zsh and ...) have built-in commands which override commonly-available utilities, and provide separate documentation for those builtins. Both the builtin and the executable utility should implement the subset of functionality required by Posix. See sections 2 and 4 of http://pubs.opengroup.org/onlinepubs/9699919799/idx/xcu.html – rici Oct 20 '14 at 19:22