Eric Blake wrote: > According to Michael Williams on 9/12/2007 4:06 AM: > > Being that I'm not a bash (or any other shell for that matter) guru, is > > there any reason that parsing occurs this way? Why is it not more like > > other programming languages? > > Two words: history and POSIX. It's been done that way for more than 20 > years, so it was standardized that way. Changing it would break too many > existing scripts.
The shell is first and foremost a way to launch other commands. The syntax is simply "if" followed by a command-list, (e.g. if /some/foo; or even if cmd1; cmd2; cmd3; then). Plus the '( ... )' syntax is already taken by the use of starting a subshell. As I recall in the original shell language the file test operator was not built-in. It was provided by the standalone '/bin/test' command. The result was effectively this: if /bin/test -d somedir Although the full path /bin/test was never used. I showed it that way here for emphasis that following the 'if' statement is a command list. Normally it would simply have been: if test -d somedir Of course that is fine and for the best portability that style is still the recommended way today to use the test command. But many people find that it looks different from other programming languages. To make the test operator (note I mention the test operator and not the shell language, this is a localized change not affecting the language as a whole) look more like other programming languages the 'test' program was coded to ignore the last argument if it was a ']'. Then a copy of the test program could be used as the '[' program. ...modify /bin/test to ignore ']' as last argument... cp /bin/test /bin/[ This allows: if [ -d somedir ] Doesn't that look more normal? People liked it and it caught on. It was so popular that both 'test' and '[' are now shell built-ins. They don't launch an external '/bin/test' program anymore. But they *used* to launch external programs. Therefore argument parsing is the same as if they still did launch an external program. This affects argument parsing. it test -f *.txt test: too many arguments Oops. I have twenty .txt files and so test got one -f followed by the first file followed by the remaining files. (e.g. test -f 1.txt 2.txt 3.txt 4.txt) if test -d $file test: argument expected Oops. I meant to set file. file=/path/some/file if test -d $file If variables such as that are not set then they wlll be expanded by the shell before passing them to the (possibly external) command and disappear entirely. This is why test arguments should always be quoted. if test -d "$file" if [ -d "$file" ] Actually today test is defined that if only one argument is given as in this case "test FOO" then then test returns true if the argument is non-zero in text length. Because "-d" is non-zero length "test -d" is true. The number of arguments affects how test parses the args. This avoids a case where depending upon the data may look like a test operator. DATA="something" if test "$DATA" # true, $DATA is non-zero length DATA="" if test "$DATA" # false, $DATA is zero length But the problem case is how should test handle an argument that looks like an operator? This used to generate errors but now because it is only one argument is defined to be the same as test -n $DATA. DATA="-d" if test "$DATA" # true, $DATA is non-zero length if test -d # true, same as previous case. Because test and [ are possibly external commands all of the parts of them are chosen to avoid shell metacharacters. The Fortran operator naming was well known at the time (e.g. .gt., .eq., etc.) and was pressed into service for the shell test operator too. Comming from Fortran using -gt, -eq, etc. looked very normal. Incorrect use generating unlikely to be intended results: if test 5 > 2 # true, "5" is non-zero length, creates file named "2" Intended use: if test 5 -gt 2 # true (and no shell meta characters needing quoting) Then much later, sometime in the mid 1980's, the Korn sh decided to improve upon this situation. A new test operator was introduced. This one was always a shell built-in and therefore could act upon the shell arguments directly. This is '[[' which is a shell keyword. (Keyword, metacharacters, builtins, all are different.) Because the shell processes [[ internally all arguments are known and do not need to be quoted. if [[ -d $file ]] # okay if [[ 5 > 2 ]] # okay I am sure that I am remembering a detail wrong but hopefully this is useful as a gentle introduction and interesting anyway. Bob