4 ms·
The author can't remember how to do a for-loop in Bash, so he invented something that is Python, but also isn't Python, because straight Python doesn't work in
by 0xbadcafebee 4y ago
The author can't remember how to do a for-loop in Bash, so he invented something that is Python, but also isn't Python, because straight Python doesn't work in the context of Ken Thompson's original command shell from 1971?
....just so the author doesn't have to look at StackOverflow, here is his Python example in the video:
for i in range(5):
if i:
print(i)
Here it is in POSIX Shell/Bourne Shell/Bash/whatever:
for i in 1 2 3 4 5
do
echo $i
done
The fancy version:
for i in `seq 1 5` ; do
if [ $i -gt 0 ] ; then
echo $i
fi
done
Fancier:
for i in $(seq 1 5) ; do
[ $i -gt 0 ] && echo $i
done
Mucho Fancy (Bash-only):
while read -r NUM ; do
[ "$NUM" -gt 0 ] && echo "$NUM"
done < <(seq 1 5)
Uber Fancy: Internal Seq w/Arrays & References (Bash v4+ only):
_internal_seq () {
declare -n aref="$1"
local counter="$2" end="$3"
while [ "$counter" -le "$end" ] ; do
aref+=("$counter")
counter="$((counter+1))"
done
}
declare -a numbers=()
_internal_seq numbers 1 5
for i in "${numbers[@]}" ; do
echo "$i"
done
The version I actually use as a one-liner:
for i in `seq 1 5` ; do echo $i ; done
- LanternLight83 4y agoYou can use brace expansion to avoid calling out to seq in a subshell (`{1..5}` expands to `1 2 3 4 5`)
- 0xbadcafebee 4y agoAwesome, thanks!! Just found the section of the man page, looks like a pretty feature-filled expansion technique: A sequence expression takes the form {x..y[..incr]}, where x and y are either integers or single characters, and incr, an optional increment, is an integer. When integers are sup- plied, the expression expands to each number between x and y, inclusive. Supplied integers may be prefixed with 0 to force each term to have the same width. When either x or y begins with a zero, the shell attempts to force all generated terms to contain the same number of digits, zero-padding where necessary. When characters are supplied, the expres- sion expands to each character lexicographically between x and y, inclusive, using the default C locale. Note that both x and y must be of the same type. When the increment is supplied, it is used as the difference between each term. The default increment is 1 or -1 as appropriate. ...although I just tested it, and the increment and 0-padding only work in non-interactive mode ='(
- hddqsb 4y ago> ...although I just tested it, and the increment and 0-padding only work in non-interactive mode ='( Works for me... $ echo $0 # determine current shell (alternative: "ps -p $$") bash $ echo {01..100..20} 001 021 041 061 081 But note that `{x..y}` has a major limitation: it doesn't support variables. Example: $ n=5 $ echo {1..$n} {1..5} # intended output: 1 2 3 4 5 The `for ((...))` syntax mentioned by @zazaulola supports variables (it works in bash and zsh). For completeness, here is a POSIX sh approach that doesn't require `seq` (in fact I notice this is what your "Uber Fancy" version uses internally): i=1 while [ "$i" -le 5 ]; do echo "$i" i="$((i+1))" done (but note that `continue` won't work properly).
- bheadmaster 4y agoYou can always do double eval for variables: var=1234 echo `echo {1..$var}`
- 0xbadcafebee 4y agoYup, which you could also write as eval echo {1..$var} . The second argument's variable is being interpolated as if it's a quoted string, before it gets passed to eval, so eval is actually seeing "{1..1234}" and not doing the interpolation itself (because it wouldn't, because the variable isn't valid in a range). This should prevent the need for a subshell and the second echo call. With an array: var=12 declare -a array=() eval "array=( {01..$var} )" echo "${array[@]}" # 01 02 03 04 05 06 07 08 09 10 11 12
- 0xbadcafebee 4y agoAhhh, my login shell is bash v3 (friggin' MacOS!) but my PATH has bash v5! On Bash v5 it works interactively! (probably v4+ too)
- zazaulola 4y agoFor me, the most beautiful representation of a loop is like this: for((x=1;x<=5;x++)) do if [[ -v x ]] then echo $x fi done
- hddqsb 4y agoTip: You can use { } instead of do/done to emulate C syntax. Aside: [[ -v x ]] tests if the variable x is set. It should probably be [[ x -ne 0 ]] to match the example from the parent.
- cassepipe 4y ago"You can use { } instead of do/done" In sh ? In bash ? In zsh ? I am very glad if it is so in any of them
- hddqsb 4y agoIt works in bash/zsh/ksh but not in sh/dash. Examples: for i in 1 2 3 4 5; { echo $i; } for ((i=1;i<=5;i++)) { echo $i; } (It doesn't work with `while` loops, at least not in bash.)
- cardanome 4y agoI strongly dislike this style. Way too easy to commit a one off error. Personally, I never use naked loops if I can avoid them. Either foreach or higher order functions if the language has them.
- twic 4y agoSurely the version you would actually use is: seq 5
- omegalulw 4y agoPresumably the point is to do something with i rather than just printing.
- lambdaba 4y agoseq 5 | xargs -n1 <...>
- dTal 4y agoVery beautiful. Of course now you have to deal with xargs' wacky syntax and options for string interpolation, which is probably why people don't do this. Compare: for i in {1..5}; do echo file_$i; done with seq 5 | xargs -I % echo file_% I had to look up the manpage for xarg's -I, while the for loop uses a small number of orthogonal features entirely in my "working set". This is also why people "uselessly" use cat.
- benj111 4y agoI'm not sure whether you're trying to prove the authors point or not? For myself I can generally write basic python (or c) without having to double check. Shell not so much. Eg Does the semi colon come before or after the do? Then there's the quoting rules and expansions etc.
- 0xbadcafebee 4y agoAll shell scripting is just running commands in a shell terminal. The language is designed as if you're at a VT100 terminal typing commands in by hand. Pressing 'Enter' key tells the shell to evaluate whatever was just typed in. A semi-colon is the equivalent of the 'Enter' key. Most commands allow you to pass arguments to them (and so don't need a semi-colon after) unless they are the end of a block, like done, fi, esac, etc, which don't take arguments, so they would get a semi-colon or newline after them. Most things want some whitespace between each element, so for example "[1-lt2]" doesn't parse, but " [ 1 -lt 2 ] " does. If you read the man page from top to bottom, pretty much all your questions will go away. Bash is only as confusing and mysterious as every other language. Imagine trying to use C++ without ever reading a book on how C++ works. Bash is much simpler! But you should read the man page!