I'm trying to make a countdown timer script that takes a number of seconds as $1, then counts down to zero, showing the current remaining seconds as it goes.
The catch is, I'm doing this on an embedded box that doesn't have seq or jot, which are the two tools I know can generate my list of numbers.
Here's the script as I have it working on a normal (non-embedded) system:
#!/bin/sh
for i in $(/usr/bin/jot ${1:-10} ${1:-10} 1); do
printf "\r%s " "$i"
sleep 1
done
echo ""
This works in FreeBSD. If I'm on a Linux box, I can replace the for line with:
for i in $(/usr/bin/seq ${1:-10} -1 1); do
for the same effect.
But what do I do if I have no jot OR seq?
The problem with "vanilla" Bourne shell is that there's no such thing; the behavior depends on the particular implementation. Most modern /bin/shes have POSIX features, but differ in the details. I have a habit of falling back to really ancient Bourne features when I go into "sh-compatibility" mode, which was helpful 30 years ago but is usually overboard on modern systems, even embedded ones. :)
Anyway, here's a generic countdown loop that works even in very old shells, yet still works fine in modern bash/dash/ksh/zsh/etc. It does require the expr command, which is a pretty safe requirement.
i=10
while [ $i -gt 0 ]; do
...
i=`expr $i - 1`
done
So if your embedded system has printf, here's your script, with the same "default to 10 if no argument specified" behavior:
#!/bin/sh
n=$1
i=${n-10}
while [ $i -gt 0 ]; do
printf '\r%s ' "$i"
i=`expr $i - 1`
sleep 1
done
(The first two lines can probably be replaced with just i=${1-10}, but some - again, ancient - shells didn't like applying special parameter expansions to numeric parameters.)
If the system doesn't have printf, then it becomes problematic; with only the shell's built-in echo (or no builtin and some randomly selected implementation of /bin/echo), there's no guaranteed way to do either of those things (echo a special character or prevent the newline). You might be able to use \r or \015 to get the carriage return. You might need -e to get the shell to recognize those (but that might just wind up echoing a literal -e). Putting a literal carriage return inside the script will probably work but makes maintaining the script a pain.
Similarly, -n might squash the newline, but might just echo a literal -n. The earliest way to squash newlines with echo used the special sequence \c where the newline would naturally go; this still works with some versions of /bin/echo (including the one on Mac OS X) or in conjunction with bash's builtin echo's -e option.
So what seems to be the simplest part of the script might be the part that makes you reach for awk.
If your shell was bash, you could count down from a fixed number with something like this:
#!/bin/bash
for n in {10..1}; do
printf "\r%s " $n
sleep 1
done
But that won't work for you, because bash won't handle things like {${1:-10}..1}, and you've specified you want a command line option.
Of course, you've also said you're not using bash, so we'll assume a simpler shell.
If you have awk, you can use that to count.
#!/bin/sh
for n in $(awk -v m="${1:-10}" 'BEGIN{for(;m;m--){print m}}'); do
printf "\r%s " $n
sleep 1
done
printf "\r \r" # clean up
If you don't have awk, you should be able to do it in pure shell:
#!/bin/sh
n=${1:-10}
while [ $n -gt 0 ]; do
printf "\r%s " $n
sleep 1
n=$((n-1))
done
printf "\r \r" # clean up
I think the pure-shell version is probably simple enough that it should be preferred over the awk solution.
Of course, as Mark Reed pointed out in comments, if your system doesn't include a printf, then you'll need to perform some ugly echo magic that will depend on your OS or shell... and if your shell doesn't support $((..)), you can replace that line with n=$(expr $n - 1). If you want to add error handling in case a non-numeric $1 is provided, that wouldn't hurt.
If you're using Bash, this is what you want:
for i in {1..10}
Or no bash? How about the stuff from this post? Bourne Shell For i in (seq)
try
for i in 1 10 15 20
do
echo "do something with $i"
done
else if you have recent Solaris, there is bash 3 at least. for example
this give range from 1 to 10 and 15 to 20
for i in {1..10} {15..20}
do
echo "$i"
done
OR use tool like nawk
for i in `nawk 'BEGIN{ for(i=1;i<=10;i++) print i}'`
do
echo $i
done
OR even the while loop
while [ "$s" -lt 10 ]; do s=`echo $s+1|bc`; echo $s; done
Related
Here is myscript.sh
#!/bin/bash
for i in {1..$1};
do
echo $1 $i;
done
If I run myscript.sh 3 the output is
3 {1..3}
instead of
3 1
3 2
3 3
Clearly $3 contains the right value, so why doesn't for i in {1..$1} behave the same as if I had written for i in {1..3} directly?
You should use a C-style for loop to accomplish this:
for ((i=1; i<=$1; i++)); do
echo $i
done
This avoids external commands and nasty eval statements.
Because brace expansion occurs before expansion of variables. http://www.gnu.org/software/bash/manual/bashref.html#Brace-Expansion.
If you want to use braces, you could so something grim like this:
for i in `eval echo {1..$1}`;
do
echo $1 $i;
done
Summary: Bash is vile.
You can use seq command:
for i in `seq 1 $1`
Or you can use the C-style for...loop:
for((i=1;i<=$1;i++))
Here is a way to expand variables inside braces without eval:
end=3
declare -a 'range=({'"1..$end"'})'
We now have a nice array of numbers:
for i in ${range[#]};do echo $i;done
1
2
3
I know you've mentioned bash in the heading, but I would add that 'for i in {$1..$2}' works as intended in zsh. If your system has zsh installed, you can just change your shebang to zsh.
Using zsh with the example 'for i in {$1..$2}' also has the added benefit that $1 can be less than $2 and it still works, something that would require quite a bit of messing about if you wanted that kind of flexibility with a C-style for loop.
Attempting to make a "simple" parallel function in bash. The problem is currently that when the line to capture the output is backgrounded, the output is lost. If that line is not backgrounded, the output is captured fine, but this of course defeats the purpose of the function.
#!/usr/bin/env bash
cluster="${1:-web100s}"
hosts=($(inventory.pl bash "$cluster" | sort -V))
cmds="${2:-uptime}"
parallel=10
cx=0
total=0
for host in "${hosts[#]}"; do
output[$total]=$(echo -en "$host: ")
echo "${output[$total]}"
output[$total]+=$(ssh -o ConnectTimeout=5 "$host" "$cmds") &
cx=$((cx + 1))
total=$((total + 1))
if [[ $cx -gt $parallel ]]; then
wait >&/dev/null
cx=0
fi
done
echo -en "***** DONE *****\n Results\n"
for ((i=0; i<= $total; i++)); do
echo "${output[$i]}"
done
That's because your command (the assignment) is run in a subshell, so this assignment can't influence the parent shell. This boils down to this:
a=something
a='hello senorsmile' &
echo "$a"
Can you guess what the output is? the output is, of course,
something
and not hello senorsmile. The only way for the subshell to communicate with the parent shell is to use an IPC (interprocess communication), in one form or another. I don't have any solution to propose, I only tried to explain why it fails.
If you think of it, it should make sense. What do you think of this?
a=$( echo a; sleep 1000000000; echo b ) &
The command immediately returns (after forking)... but the output is only going to be fully available in... over 31 years.
Assigning a shell variable in the background this way is effectively meaningless. Bash does have built in co-processing which should work for you:
http://www.gnu.org/software/bash/manual/bashref.html#Coprocesses
I have read a line of bash code from the file, and I want to send it to log. To make it more useful, I'd like to send the variable-expanded version of the line.
I want to expand only shell variables. I don't want the pipes to be interpreted, nor I don't want to spawn any side processes, like when expanding a line with $( rm -r /).
I know that variable expansion is very deeply woven into the bash. I hope there is a way to perform just expansion, without any side effects, that would come from pipes, external programs and - perhaps - here-documents.
Maybe there is something like eval?
#!/bin/bash
linenr=42
line=`sed "${linenr}q;d" my-other-script.sh`
shell-expand $line >>mylog.log
I want a way to expand only the shell variables.
if x=a, a="example" then I want the following expansions:
echo $x should be echo a.
echo ${a} should be echo example
touch ${!x}.txt should be touch example.txt
if [ (( ${#a} - 6 )) -gt 10 ]; then echo "Too long string" should be if [ 1 -gt 10 ]; then echo "Too long string"
echo "\$a and \$x">/dev/null should be echo "\$a and \$x>dev/null"
For those arriving five years later, and, although it is not the best answer to the OP's problem, an answer to the question is as follows. Bash can do indirect parameter expansion:
some_param=a
a=b
echo ${!some_param}
echo $BASH_VERSION
# 5.0.18(1)-release
You are absolutely right, it is very dangerous to use the bash.
In fact your command suffers from your problem.
Let us discuss your script in detail:
#!/bin/bash
line=`sed "${42}q;d" my-other-script.sh`
shell-expand $line >>mylog.log
The sed may produce many lines of output, so it is misleading to use the name line.
Then you did not quote $line, this may have obscure effects:
$ x='| grep x'
$ ls -l $x
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
-rw-rw-r-- 1 foo bar 34493 Nov 19 18:51 x
$
In this case the pipe is not executed, but passed to the program ls.
If you have untrusted input, it is very hard to program a robust shell script.
Using eval is evil - I would never suggest using it, especially for such a purpose!
An alternative way would be in perl, iterate over the $ENV array and replace all env keys by the env values. This way you have more control over the things, which may happen.
I am new to Unix programming and I am not able to figure out what is wrong with this code:
#!/bin/sh
i=1
max=10
for i in {1..$max}
do
echo $i;
done
If I try the above code as follows it works:
#!/bin/sh
i=1
max=10
for i in {1..10}
do
echo $i;
done
I have tried this:
#!/bin/sh
i=1
max=10
for i in {1..`echo $max`}
do
echo $i;
done
and
#!/bin/sh
i=1
max=10
for i in {1..`expr $max`}
do
echo $i;
done
and
#!/bin/sh
i=1
max=10
for i in {1..`echo $max | bc`}
do
echo $i;
done
But it is also not working.. Can anyone tell me how come it will work..?
Bash/zsh support much more faster and flexible form:
for ((i=1; i<$max; ++i));
Don't use external commands (like seq), or backticks - it will slow down your script.
Maybe you can try this
#!/bin/sh
max=10
for i in $(seq 1 $max)
do
echo "$i"
done
You can see this answer
brace expansion, {x..y} is performed before other expansions, so you cannot use that for variable length sequences.
Update:
In the case of you want a sequence of custom increment, the man page of seq gives the following:
seq [-w] [-f format] [-s string] [-t string] [first [incr]] last
Therefore you can use seq 1 3 $max to get a sequence with increment 3.
In general,
#!/bin/sh
max=10
incr=3
for i in $(seq 1 $incr $max)
do
echo "$i"
done
Sequence expressions of the form {x..y} only take place when x and y are literal numbers or single characters. It takes place before variable expansion. If the limits can include variables, use the seq command:
for i in $(seq 1 $max)
The bash man page says this:
Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly tex-
tual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.
So it appears that bash just passes through verbatim whatever text is in the braces. I wasn't familiar with that syntax, and I had to look it up. It doesn't sound like it was intended to be used in for loops.
bash has newer ways of doing this, but the traditional way is
for i in $(seq 1 $max)
do
# whatever...
done
Since pretty much anything can be done in bash with enough effort, and I couldn't turn down the challenge, here's how you could do it with braces anyway:
for i in $(eval echo {1..$max})
do
echo $i
done
Suppose I have a #!/bin/sh script which can take a variety of positional parameters, some of which may include spaces, either/both kinds of quotes, etc. I want to iterate "$#" and for each argument either process it immediately somehow, or save it for later. At the end of the script I want to launch (perhaps exec) another process, passing in some of these parameters with all special characters intact.
If I were doing no processing on the parameters, othercmd "$#" would work fine, but I need to pull out some parameters and process them a bit.
If I could assume Bash, then I could use printf %q to compute quoted versions of args that I could eval later, but this would not work on e.g. Ubuntu's Dash (/bin/sh).
Is there any equivalent to printf %q that can be written in a plain Bourne shell script, using only built-ins and POSIX-defined utilities, say as a function I could copy into a script?
For example, a script trying to ls its arguments in reverse order:
#!/bin/sh
args=
for arg in "$#"
do
args="'$arg' $args"
done
eval "ls $args"
works for many cases:
$ ./handle goodbye "cruel world"
ls: cannot access cruel world: No such file or directory
ls: cannot access goodbye: No such file or directory
but not when ' is used:
$ ./handle goodbye "cruel'st world"
./handle: 1: eval: Syntax error: Unterminated quoted string
and the following works fine but relies on Bash:
#!/bin/bash
args=
for arg in "$#"
do
printf -v argq '%q' "$arg"
args="$argq $args"
done
eval "ls $args"
This is absolutely doable.
The answer you see by Jesse Glick is approximately there, but it has a couple of bugs, and I have a few more alternatives for your consideration, since this is a problem I ran into more than once.
First, and you might already know this, echo is a bad idea, one should use printf instead, if the goal is portability: "echo" has undefined behavior in POSIX if the argument it receives is "-n", and in practice some implementations of echo treat -n as a special option, while others just treat it as a normal argument to print. So that becomes this:
esceval()
{
printf %s "$1" | sed "s/'/'\"'\"'/g"
}
Alternatively, instead of escaping embedded single quotes by making them into:
'"'"'
..instead you could turn them into:
'\''
..stylistic differences I guess (I imagine performance difference is negligible either way, though I've never tested). The resulting sed string looks like this:
esceval()
{
printf %s "$1" | sed "s/'/'\\\\''/g"
}
(It's four backslashes because double quotes swallow two of them, and leaving two, and then sed swallows one, leaving just the one. Personally, I find this way more readable so that's what I'll use in the rest of the examples that involve it, but both should be equivalent.)
BUT, we still have a bug: command substitution will delete at least one (but in many shells ALL) of the trailing newlines from the command output (not all whitespace, just newlines specifically). So the above solution works unless you have newline(s) at the very end of an argument. Then you'll lose that/those newline(s). The fix is obviously simple: Add another character after the actual command value before outputting from your quote/esceval function. Incidentally, we already needed to do that anyway, because we needed to start and stop the escaped argument with single quotes. You have two alternatives:
esceval()
{
printf '%s\n' "$1" | sed "s/'/'\\\\''/g; 1 s/^/'/; $ s/$/'/"
}
This will ensure the argument comes out already fully escaped, no need for adding more single quotes when building the final string. This is probably the closest thing you will get to a single, inline-able version. If you're okay with having a sed dependency, you can stop here.
If you're not okay with the sed dependency, but you're fine with assuming that your shell is actually POSIX-compliant (there are still some out there, notably the /bin/sh on Solaris 10 and below, which won't be able to do this next variant - but almost all shells you need to care about will do this just fine):
esceval()
{
printf \'
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
printf \'
}
You might notice seemingly redundant quoting here:
printf %s "${unescaped%%\'*}""'\''"
..this could be replaced with:
printf %s "${unescaped%%\'*}'\''"
The only reason I do the former, is because one upon a time there were Bourne shells which had bugs when substituting variables into quoted strings where the quote around the variable didn't exactly start and end where the variable substitution did. Hence it's a paranoid portability habit of mine. In practice, you can do the latter, and it won't be a problem.
If you don't want to clobber the variable unescaped in the rest of your shell environment, then you can wrap the entire contents of that function in a subshell, like so:
esceval()
{
(
printf \'
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
printf \'
)
}
"But wait", you say: "What I want to do this on MULTIPLE arguments in one command? And I want the output to still look kinda nice and legible for me as a user if I run it from the command line for whatever reason."
Never fear, I have you covered:
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
printf %s "$1" | sed "s/'/'\\\\''/g"
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}
..or the same thing, but with the shell-only version:
esceval()
{
case $# in 0) return 0; esac
(
while :
do
printf "'"
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
)
}
In those last four, you could collapse some of the outer printf statements and roll their single quotes up into another printf - I kept them separate because I feel it makes the logic more clear when you can see the starting and ending single-quotes on separate print statements.
P.S. There's also this monstrosity I made, which is a polyfill which will select between the previous two versions depending on if your shell seems to be capable of supporting the necessary variable substitution syntax (it looks awful though, because the shell-only version has to be inside an eval-ed string to keep the incompatible shells from barfing when they see it): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh
I think this is POSIX. It works by clearing $# after expanding it for the for loop, but only once so that we can iteratively build it back up (in reverse) using set.
flag=0
for i in "$#"; do
[ "$flag" -eq 0 ] && shift $#
set -- "$i" "$#"
flag=1
done
echo "$#" # To see that "$#" has indeed been reversed
ls "$#"
I realize reversing the arguments was just an example, but you may be able to use this trick of set -- "$arg" "$#" or set -- "$#" "$arg" in other situations.
And yes, I realize I may have just reimplemented (poorly) ormaaj's Push.
Push. See the readme for examples.
The following seems to work with everything I have thrown at it so far, including spaces, both kinds of quotes and a variety of other metacharacters, and embedded newlines:
#!/bin/sh
quote() {
echo "$1" | sed "s/'/'\"'\"'/g"
}
args=
for arg in "$#"
do
argq="'"`quote "$arg"`"'"
args="$argq $args"
done
eval "ls $args"
If you're okay with calling out to an external executable (as in the sed solutions given in other answers), then you may as well call out to /usr/bin/printf. While it's true that the POSIX shell built-in printf doesn't support %q, the printf binary from Coreutils sure does (since release 8.25).
esceval() {
/usr/bin/printf '%q ' "$#"
}
We can use /usr/bin/printf when version of GNU Coreutil is not less than 8.25
#!/bin/sh
minversion="8.25"
gnuversion=$(ls '--version' | sed '1q' | awk 'NF{print $NF}')
printcmd="printf"
if ! [ $gnuversion \< $minversion ]; then
printcmd="/usr/bin/printf"
fi;
params=$($printcmd "%q" "$#")