l***@gmail.com
2006-03-06 17:49:29 UTC
Hi,
A little more than a year ago, I posted a message to this group about
fish, a new shell I'd written. (See
http://groups.google.se/group/comp.unix.shell/browse_frm/thread/d4ede40181eb5001/7ba776d029e871dd?q=fish&rnum=1#7ba776d029e871dd)
At the time, fish was what I would describe as a program with some nice
interactive features, and a quite lousy language syntax. Much of the
focus of the following discussion on this list focused on the syntax,
not on the new interactive features, such as syntax highlighting,
advanced tab completions, etc.. The new UI features had been the main
focus of my efforts so far, which is why the shell syntax was in the
state it was. In retrospect, I did not make it clear that while there
where some interesting new syntax ideas, the syntax itself should have
been considered a placeholder. It should also be noted that during the
course of the original conversation, it was demonstrated to me that zsh
already contained several of the features that I thought where specific
to fish, only they where not enabled by default and the way to enable
them was not obvious to me. Fish still has plenty of unique UI
features, though.
Since then, I've spent a large amount of time hacking on fish, and many
others have also helped out with patches and opinions and counter
arguments, and the fish shell is very different from how it was one
year ago. Most of these changes are related to the fish syntax. When
rewriting the fish syntax, I had the choice of either using a
Posix-like syntax, using the somewhat less broken rc syntax or doing
something completely new. Given the number of bike sheds in the world,
and the well known NIH-syndrome, it should not surprise anyone that I
chose the latter option, though I have tried to use the Posix syntax in
all places where I do not think it is completely broken. There are a
small number of changes that mostly change something that is not
really, broken, where I simply thought the new syntax to be a bit
better. Perhaps these where mistakes, I am unsure. The most noticable
such change is the use of () instead of $() for command substitutions.
Still, I hope that when reading up on the fish syntax, people will
agree with me that overall going for a new syntax was the right thing
to do. I am very curious about what the people in this group think
about fish the way it works now. Here are the main syntax features of
fish compared to Posix-like shells like bash:
No Macro language pretence:
Two of the most common issues with Posix shells I've seen are caused by
the roots of shells as macro expansion languages, even though at least
bash is not internally implemented as a macro expansion language. The
first of these is the ever present requirement of using quotes around
variables to avoid argument separation on spaces. This is just silly,
it is almost never what you want, and in the few rare cases where it
is, it can be achived with a command substitution and a pipe through
tr. So fish never separates arguments on spaces in variable values.
The second such common example is when doing something like:
bash> FOO=BAR
bash> FOO=BAZ; echo $FOO
BAR
This makes a lot of sense if you consider the shell as a macro
expansion language that works on a line-by-line basis, but that does
not make it a good syntax.
The fact that bash has fixed the second of these two misfeatures, but
not the first just makes things worse in my opinion, since it makes it
clear that bash is not a macro expansion language, it is simply a
language that pretendes to be one. In my opinion, it makes the language
unpredictable, since you never know without testing if bash will behave
like a macro exapnder or not without trying.
If I'm not mistaken (I often am when it comes to zsh, I'm sorry in
advance for all the misrepresentations of zsh that I'm sure exist in
this post), zsh allows you to choose how it behaves in both these
cases. This is a recuring theme with zsh, at least it seems that way to
me. The philosophy of always answering the question 'Which of these two
paths should we choose?' with 'Both!' is suboptimal as I see it. I
don't think that it is a good idea to make the syntax of programming
languages configurable. If you do, you can't safely run a piece of
shellscript without checking that the scripter used the same settings
as you do. Also, the zsh approach means that everyone has to take the
time to tweak every little detail of the shell to their liking (since
the defaults are rarely very good), and that if they are stuck on a
foreign host, they will be lost since everything will be tweaked all
wrong. In my opinion, it is often better to choose one way of doing
something and sticking with it. If it turns out to be the wrong way (A
conclusion that should _never_ be reached without a great deal of
thought), implement a new one and eventually _remove_ the old, bad way
of doing it.
Scoping:
Posix shells have no notion of local variables, and instead use
subshells to get something more or less like read-only variables in
some situations. Unless I'm mistaken, bash allows you to create local
variables as an extension, but the main way of providing variable scope
is still to use subshells, either through () or using command
substitution or simply by using a pipeline. It should also be noted
that Posix does not define the behaviour of pipelines with regards to
forking of subshells, and that bash and zsh do this differently.
Specifically, the following works in zsh but is meaningless in bash:
cat file.txt|read contents
In fish all variables have scope. By default, when creating a new
variable the scope is local to the currently running function, or if no
function is running, the scope is global. You can explicitly set the
scope of a variable using -g (global) or -l (local to the current block
of commands). There are _no_ subshells in fish, i.e. fish never forks
of a subprocess other than to execute a command in it. This means that
you can freely change variable values inside command substitutions and
pipes, and these changes will be visible in the shell proper.
I think that while some people may be used to them, the traditional
shell scoping rules are both error prone (since large shellscripts will
often have problems with variable name clashes because most variables
end up as globals) and limiting (since you often can not rely on
variable writes being permanent - if you do you will limit your
function to never be used in pipelines or in command substitutions),
and that the type of scoping provided in fish, as well as nearly every
other procedural or object oriented language on the planet, is often
both safer and more powerful. As an extra bonus, many people use other
languages with similar scoping rules to fish, meaning that they will
already be familiar with the fish scoping rules.
Block commands:
Fish changes the way blocks of code are defined. Examples:
if foo; then bar; fi -> if foo; bar; end
case $foo; a) bar;; *) baz;; esac -> switch $foo; case a; bar;
case '*'; baz; end
foo(){ bar; } -> function foo; bar; end
You will notice that 'fi', 'esac', 'done', '}' and all the other block
end commands have been replaced by 'end'. The updated block syntax is
inspired by Matlab and Lua. The really major benefit, in my opinion, is
with function definitions. Quickly now, which of the following are
legal function definitions?
hello () {echo hello }
hello () {echo hello;}
hello () {;echo hello }
hello () {;echo hello;}
hello () { echo hello }
hello () { echo hello;}
hello () { ;echo hello }
hello () { ;echo hello;}
The answer is - only number six. At least in my book, that is proof of
a completely broken syntax.
Fish also drops words like 'then' and 'do', which make the code look
less like code and more like english at the expense of brevity and
consistence. I have never found these extra keywords to be more helpful
than 'please' is in Intercal.
Everything is a command:
Here are a few more fish syntax changes:
foo=bar -> set foo bar
make && make install -> make; and make install
These changes exist to make the fish syntax more consistent.
Specifically, in fish pretty much everything is a command. Variable
assignemnts, loops and conditionals are all regular builtin commands. I
firmly belive that by making as much of the syntax as possible obey the
same rules, the language becomes more predictable and easier to learn.
It could be argued that this is trying to fit a round peg in a square
hole, bit I don't think that is the case, I've found that all these
tasks are very suitable for builtin commands. Also, it is much easier
to get help on a command than on some weird piece of syntactic sugar.
Want to know how to use the 'set' builtin? type 'set --help' and you'll
get it.
Nicer array variables:
In fish, all variables are really arrays. To define a variable 'foo'
with the elements 'a', 'b' and 'c', simply use
set foo a b c
$foo is expanded to all the elements of the array as separate elements.
Use [] to acces an element in the array, e.g. $foo[1]. You can specify
multiple elements, e.g. $foo[1 3], and you can use the seq command
inside a command substitution to slice the array, e.g. $foo[(seq 2)].
You can also set and erase parts of the array. For example, the
following loops over the $argv vector until it is empty
while count $argv >/dev/null
switch $argv[1]
...
end
set -e foo[1]
end
Compare this with the bash syntax where you use syntax that is
non-obviously (to me, at least) different from the regular variable
syntax ro reference and assign arrays, and it is hardly surprising that
very few people seem to use arrays in bash.
An extra bonus in fish is that all variables inherited from the parent
process are turned into arrays using ':' as the array separator. That
means that PATH, CDPATH, LS_COLORS and various other lists are treated
as arrays by fish. When exported to subcommands, all arrays are of
course concatenaded together using ':' as the join character.
Universal variables:
Since the dawn of time, clueless users have asked questions like 'how
can I change an environment varible in another running process?' The
answer has always been variations of 'You don't.' No longer so in fish.
Fish supports universal variables, which are variables whose value is
shared between all running fish instances with the specified user on
the specified machine. Universal variables are automatically saved
between reboots and shutdowns, so you don't need to put their values in
an init file. Universal variables have the outermost scope, meaning the
will never get used in preference of shell-specific variables, which
should minimize security implications.
Universal variables make it much more practical to use environment
variables for configuration options. Youy simply change an environemnt
variable in one shell, and the change will propagate to all sunning
shells, and it will be saved so that the new value is used after a
reboot as well. One example of environemnt variables in action can be
had by launching two fish instances in separate terminals side-by-side.
The issue the command 'set fish_color_cwd blue' and the color of the
current working directory element of the prompt will change color to
blue in both shells. Using universal variables makes it much more
convenient to set configuration options like $BROWSER, $PAGER and
$CDPATH.
Events:
Fish allows you to trigger a shellscript function at a specific time,
such as at the completion of a specific job or process, when a specific
variable changes value or when a specific signal is recieved. The
syntax for this is:
function winch_handler --on-signal WINCH; echo WINCH; end
function browser_handler --on-variable BROWSER; echo new browser is
$BROWSER; end
function process_exit_handler --on-process 123; echo process 123 died;
end
For example, bash process substitution is not natively supported by
fish, but a workalike, implemented in shellscript, is included with
fish:
function psub
# By setting the variables here, we set their scope to function local.
# This means they won't overwrite any existing global variables with
# the same name.
set -l filename
set -l funcname
# Find unique file name for writing output to
while true
set filename /tmp/.psub.(echo %self).(random);
if not test -e $filename
break;
end
end
mkfifo $filename
cat >$filename &
echo $filename
# Find unique function name
while true
set funcname __fish_psub_(random);
if not functions $funcname >/dev/null ^/dev/null
break;
end
end
# Make sure we remove fifo when caller exits
eval "function $funcname --on-job-exit caller; rm $filename; functions
--erase $funcname; end"
end
The above allows you to replace this
diff <(sort foo.txt) <(sort bar.txt)
with
diff (sort foo.txt|psub) (sort bar.txt|psub)
This is very slightly less efficient, since the output is filtered
through the cat command, but it never touches disk since a fifo is
used, and all commands can run concurrently, so the eficcency is still
more than acceptable.
A workalike of the Posix 'trap' builtin is also implemented as a
shellscript wrapper around event handlers. Being able to trigger
function calls on diverse types of events is, in my opinion, a rather
powerful language feature, and it is one I hope to extend with new
trigger types in the future.
Error reporting:
Fish tries to help the user by being verbose and specific in it's error
messages. Here are a few examples:
Trying to use Posix-style variable assignment tells you how to use fish
style variable assignments:
fish> foo=bar
fish: Unknown command 'foo=bar'. Did you mean 'set VARIABLE VALUE'?
For information on setting variable values, see the help section on
the set command by typing 'help set'.
Using Posix short circut operators, Posix style command substitution
and various other features that use a different syntax in fish will
also give such error messages with pointers to how to do the same thing
in fish.
Fish also provides you with stack traces on errors:
fish> . foo.fish
fish: Unknown command 'abc123'
/home/axel/code/c/fish_current/foo.fish (line 5): abc123
^
in function 'do_something',
called on line 7 of file
'/home/axel/code/c/fish_current/foo.fish',
in . (source) call of file '/home/axel/code/c/fish_current/foo.fish',
called on standard input,
Autoloaded functions:
Fish functions and command specific completions can be placed in
special directories, specified using the environment variables
$fish_function_path and $fish_complete_path. Such files are autoloaded
on the first invocation of completions for the command, or the first
invocation of the function. The modification time of each such file is
also tracked, so that if the file is changed, or a new one is added in
a directory with higher priority in the path, the relevant file is
reloaded. This allows one to write a huge amount of code in shellscript
without increasing either memory usage or startup time and more
importantly, without forcing the user to turn on specific features
manually. Features that aren't used will not take up memory or
processing power. Fish ships with many thousand lines of shellscript
code, but only a few hundered lines are run on startup.
Dropped features:
Fish tries to consolidate multiple strongly related Posix features into
one. One such consolidation is dropping dollar-quotes like $'\n' in
favor of allowing backslash escapes in regular strings, e.g. \n and
\x20 both work as you would expect. Other dropped features include
subshells (use fish scoping or a block of commands), math mode (use the
bc or expr commands), here documents (Use quotes or, in order to paste
large amounts of data possibly containing quotes and other illegal
characters into the shell, use ^Y to paste from the X clipboard) and
process substitution (Use the psub workalike described above).
Failiures:
While implementing new syntax features for fish, a great many different
ideas where tried out. Most of the ideas that I tried out turned out to
be bad ones, and they where dropped. I have often heard the argument
against chaing the Posix syntax that the original designers knew what
they where doing, and that chaning things will only make them worse.
This is true in the sense that if one would simply try out every gret
new idea one gets, and then make it stick around forever, even if it
turns out that the idea was a bad one, things will degenerate quickly.
But one _can_ to some extend separate the wheat from the chaff. I have
presented some of what I concluded to be wheat above. For those that
are interested, here follows some of the chaff:
Originally, when fish encountered a wildcard that had no matches, it
would silently remove that argument. This makes a huge amount of sense
sometimes, eg. when doing things like 'for i in *.txt', but does not do
what you would like when using e.g. 'ls *.txt'. I do not like the bash
method of leaving such arguments unexpanded, since it relies on the
called commands to detect the wildcard and try to report a meaningful
error. This can lead to very confusing behaviour. Instead, I've opted
to follow the same path chosen by csh, namely if all waldcards to a
specific command fail to match, the command is not executed. In
interactive mode, a warning is printed as well. This is just one of the
many things I feel csh got right. Too bad that the number of things csh
got horribly wrong is even greater...
Originally, fish made no difference between single quotes and double
quotes, they both turned of all types of argument expansion. I argued
that to embed strings into a quoted string, one quold simply use
printf. I aslo rargued that mising the two mixes code with text, which
is bad. I argued that it is confusing to have two types of quotes,
which are similar but not identical. In the end, I allowed variable
expansion in double quotes, because it saves keystrokes, because it
allwos you to make sure array variables get expanded into exactly one
token, no matter how many elements the array contains, and it allows me
get much more time for hacking instead of answering the same question
over and over again.
Originally, fish did not have any blocks at all. Instead, code was
given as arguments to other commands; e.g. the last argument to a for
loop was the list of commands to run in the loop. This is easy to
implement, but completely useless for longer scripts.
Closing comments:
There are a great many features in fish that have little to do with
syntax, like syntax highlighting, advanced tab completion, X clipboard
intergration, etc.. But this post is only mean to discuss the design
and implications of the changes made to regular shell syntax in fish.
Specifically, I'd be interested in opinions on security considerations,
regressions and further possible changes to the syntax. To try out
fish, visit http://roo.no-ip.org/fish/ or use the prepackaged version
avaialable for many systems including Debian. Fish is GPL:ed, and it
works on most Linux versions, NetBSD, FreeBSD, OS X, Solaris and
possibly Cygwin.
A little more than a year ago, I posted a message to this group about
fish, a new shell I'd written. (See
http://groups.google.se/group/comp.unix.shell/browse_frm/thread/d4ede40181eb5001/7ba776d029e871dd?q=fish&rnum=1#7ba776d029e871dd)
At the time, fish was what I would describe as a program with some nice
interactive features, and a quite lousy language syntax. Much of the
focus of the following discussion on this list focused on the syntax,
not on the new interactive features, such as syntax highlighting,
advanced tab completions, etc.. The new UI features had been the main
focus of my efforts so far, which is why the shell syntax was in the
state it was. In retrospect, I did not make it clear that while there
where some interesting new syntax ideas, the syntax itself should have
been considered a placeholder. It should also be noted that during the
course of the original conversation, it was demonstrated to me that zsh
already contained several of the features that I thought where specific
to fish, only they where not enabled by default and the way to enable
them was not obvious to me. Fish still has plenty of unique UI
features, though.
Since then, I've spent a large amount of time hacking on fish, and many
others have also helped out with patches and opinions and counter
arguments, and the fish shell is very different from how it was one
year ago. Most of these changes are related to the fish syntax. When
rewriting the fish syntax, I had the choice of either using a
Posix-like syntax, using the somewhat less broken rc syntax or doing
something completely new. Given the number of bike sheds in the world,
and the well known NIH-syndrome, it should not surprise anyone that I
chose the latter option, though I have tried to use the Posix syntax in
all places where I do not think it is completely broken. There are a
small number of changes that mostly change something that is not
really, broken, where I simply thought the new syntax to be a bit
better. Perhaps these where mistakes, I am unsure. The most noticable
such change is the use of () instead of $() for command substitutions.
Still, I hope that when reading up on the fish syntax, people will
agree with me that overall going for a new syntax was the right thing
to do. I am very curious about what the people in this group think
about fish the way it works now. Here are the main syntax features of
fish compared to Posix-like shells like bash:
No Macro language pretence:
Two of the most common issues with Posix shells I've seen are caused by
the roots of shells as macro expansion languages, even though at least
bash is not internally implemented as a macro expansion language. The
first of these is the ever present requirement of using quotes around
variables to avoid argument separation on spaces. This is just silly,
it is almost never what you want, and in the few rare cases where it
is, it can be achived with a command substitution and a pipe through
tr. So fish never separates arguments on spaces in variable values.
The second such common example is when doing something like:
bash> FOO=BAR
bash> FOO=BAZ; echo $FOO
BAR
This makes a lot of sense if you consider the shell as a macro
expansion language that works on a line-by-line basis, but that does
not make it a good syntax.
The fact that bash has fixed the second of these two misfeatures, but
not the first just makes things worse in my opinion, since it makes it
clear that bash is not a macro expansion language, it is simply a
language that pretendes to be one. In my opinion, it makes the language
unpredictable, since you never know without testing if bash will behave
like a macro exapnder or not without trying.
If I'm not mistaken (I often am when it comes to zsh, I'm sorry in
advance for all the misrepresentations of zsh that I'm sure exist in
this post), zsh allows you to choose how it behaves in both these
cases. This is a recuring theme with zsh, at least it seems that way to
me. The philosophy of always answering the question 'Which of these two
paths should we choose?' with 'Both!' is suboptimal as I see it. I
don't think that it is a good idea to make the syntax of programming
languages configurable. If you do, you can't safely run a piece of
shellscript without checking that the scripter used the same settings
as you do. Also, the zsh approach means that everyone has to take the
time to tweak every little detail of the shell to their liking (since
the defaults are rarely very good), and that if they are stuck on a
foreign host, they will be lost since everything will be tweaked all
wrong. In my opinion, it is often better to choose one way of doing
something and sticking with it. If it turns out to be the wrong way (A
conclusion that should _never_ be reached without a great deal of
thought), implement a new one and eventually _remove_ the old, bad way
of doing it.
Scoping:
Posix shells have no notion of local variables, and instead use
subshells to get something more or less like read-only variables in
some situations. Unless I'm mistaken, bash allows you to create local
variables as an extension, but the main way of providing variable scope
is still to use subshells, either through () or using command
substitution or simply by using a pipeline. It should also be noted
that Posix does not define the behaviour of pipelines with regards to
forking of subshells, and that bash and zsh do this differently.
Specifically, the following works in zsh but is meaningless in bash:
cat file.txt|read contents
In fish all variables have scope. By default, when creating a new
variable the scope is local to the currently running function, or if no
function is running, the scope is global. You can explicitly set the
scope of a variable using -g (global) or -l (local to the current block
of commands). There are _no_ subshells in fish, i.e. fish never forks
of a subprocess other than to execute a command in it. This means that
you can freely change variable values inside command substitutions and
pipes, and these changes will be visible in the shell proper.
I think that while some people may be used to them, the traditional
shell scoping rules are both error prone (since large shellscripts will
often have problems with variable name clashes because most variables
end up as globals) and limiting (since you often can not rely on
variable writes being permanent - if you do you will limit your
function to never be used in pipelines or in command substitutions),
and that the type of scoping provided in fish, as well as nearly every
other procedural or object oriented language on the planet, is often
both safer and more powerful. As an extra bonus, many people use other
languages with similar scoping rules to fish, meaning that they will
already be familiar with the fish scoping rules.
Block commands:
Fish changes the way blocks of code are defined. Examples:
if foo; then bar; fi -> if foo; bar; end
case $foo; a) bar;; *) baz;; esac -> switch $foo; case a; bar;
case '*'; baz; end
foo(){ bar; } -> function foo; bar; end
You will notice that 'fi', 'esac', 'done', '}' and all the other block
end commands have been replaced by 'end'. The updated block syntax is
inspired by Matlab and Lua. The really major benefit, in my opinion, is
with function definitions. Quickly now, which of the following are
legal function definitions?
hello () {echo hello }
hello () {echo hello;}
hello () {;echo hello }
hello () {;echo hello;}
hello () { echo hello }
hello () { echo hello;}
hello () { ;echo hello }
hello () { ;echo hello;}
The answer is - only number six. At least in my book, that is proof of
a completely broken syntax.
Fish also drops words like 'then' and 'do', which make the code look
less like code and more like english at the expense of brevity and
consistence. I have never found these extra keywords to be more helpful
than 'please' is in Intercal.
Everything is a command:
Here are a few more fish syntax changes:
foo=bar -> set foo bar
make && make install -> make; and make install
These changes exist to make the fish syntax more consistent.
Specifically, in fish pretty much everything is a command. Variable
assignemnts, loops and conditionals are all regular builtin commands. I
firmly belive that by making as much of the syntax as possible obey the
same rules, the language becomes more predictable and easier to learn.
It could be argued that this is trying to fit a round peg in a square
hole, bit I don't think that is the case, I've found that all these
tasks are very suitable for builtin commands. Also, it is much easier
to get help on a command than on some weird piece of syntactic sugar.
Want to know how to use the 'set' builtin? type 'set --help' and you'll
get it.
Nicer array variables:
In fish, all variables are really arrays. To define a variable 'foo'
with the elements 'a', 'b' and 'c', simply use
set foo a b c
$foo is expanded to all the elements of the array as separate elements.
Use [] to acces an element in the array, e.g. $foo[1]. You can specify
multiple elements, e.g. $foo[1 3], and you can use the seq command
inside a command substitution to slice the array, e.g. $foo[(seq 2)].
You can also set and erase parts of the array. For example, the
following loops over the $argv vector until it is empty
while count $argv >/dev/null
switch $argv[1]
...
end
set -e foo[1]
end
Compare this with the bash syntax where you use syntax that is
non-obviously (to me, at least) different from the regular variable
syntax ro reference and assign arrays, and it is hardly surprising that
very few people seem to use arrays in bash.
An extra bonus in fish is that all variables inherited from the parent
process are turned into arrays using ':' as the array separator. That
means that PATH, CDPATH, LS_COLORS and various other lists are treated
as arrays by fish. When exported to subcommands, all arrays are of
course concatenaded together using ':' as the join character.
Universal variables:
Since the dawn of time, clueless users have asked questions like 'how
can I change an environment varible in another running process?' The
answer has always been variations of 'You don't.' No longer so in fish.
Fish supports universal variables, which are variables whose value is
shared between all running fish instances with the specified user on
the specified machine. Universal variables are automatically saved
between reboots and shutdowns, so you don't need to put their values in
an init file. Universal variables have the outermost scope, meaning the
will never get used in preference of shell-specific variables, which
should minimize security implications.
Universal variables make it much more practical to use environment
variables for configuration options. Youy simply change an environemnt
variable in one shell, and the change will propagate to all sunning
shells, and it will be saved so that the new value is used after a
reboot as well. One example of environemnt variables in action can be
had by launching two fish instances in separate terminals side-by-side.
The issue the command 'set fish_color_cwd blue' and the color of the
current working directory element of the prompt will change color to
blue in both shells. Using universal variables makes it much more
convenient to set configuration options like $BROWSER, $PAGER and
$CDPATH.
Events:
Fish allows you to trigger a shellscript function at a specific time,
such as at the completion of a specific job or process, when a specific
variable changes value or when a specific signal is recieved. The
syntax for this is:
function winch_handler --on-signal WINCH; echo WINCH; end
function browser_handler --on-variable BROWSER; echo new browser is
$BROWSER; end
function process_exit_handler --on-process 123; echo process 123 died;
end
For example, bash process substitution is not natively supported by
fish, but a workalike, implemented in shellscript, is included with
fish:
function psub
# By setting the variables here, we set their scope to function local.
# This means they won't overwrite any existing global variables with
# the same name.
set -l filename
set -l funcname
# Find unique file name for writing output to
while true
set filename /tmp/.psub.(echo %self).(random);
if not test -e $filename
break;
end
end
mkfifo $filename
cat >$filename &
echo $filename
# Find unique function name
while true
set funcname __fish_psub_(random);
if not functions $funcname >/dev/null ^/dev/null
break;
end
end
# Make sure we remove fifo when caller exits
eval "function $funcname --on-job-exit caller; rm $filename; functions
--erase $funcname; end"
end
The above allows you to replace this
diff <(sort foo.txt) <(sort bar.txt)
with
diff (sort foo.txt|psub) (sort bar.txt|psub)
This is very slightly less efficient, since the output is filtered
through the cat command, but it never touches disk since a fifo is
used, and all commands can run concurrently, so the eficcency is still
more than acceptable.
A workalike of the Posix 'trap' builtin is also implemented as a
shellscript wrapper around event handlers. Being able to trigger
function calls on diverse types of events is, in my opinion, a rather
powerful language feature, and it is one I hope to extend with new
trigger types in the future.
Error reporting:
Fish tries to help the user by being verbose and specific in it's error
messages. Here are a few examples:
Trying to use Posix-style variable assignment tells you how to use fish
style variable assignments:
fish> foo=bar
fish: Unknown command 'foo=bar'. Did you mean 'set VARIABLE VALUE'?
For information on setting variable values, see the help section on
the set command by typing 'help set'.
Using Posix short circut operators, Posix style command substitution
and various other features that use a different syntax in fish will
also give such error messages with pointers to how to do the same thing
in fish.
Fish also provides you with stack traces on errors:
fish> . foo.fish
fish: Unknown command 'abc123'
/home/axel/code/c/fish_current/foo.fish (line 5): abc123
^
in function 'do_something',
called on line 7 of file
'/home/axel/code/c/fish_current/foo.fish',
in . (source) call of file '/home/axel/code/c/fish_current/foo.fish',
called on standard input,
Autoloaded functions:
Fish functions and command specific completions can be placed in
special directories, specified using the environment variables
$fish_function_path and $fish_complete_path. Such files are autoloaded
on the first invocation of completions for the command, or the first
invocation of the function. The modification time of each such file is
also tracked, so that if the file is changed, or a new one is added in
a directory with higher priority in the path, the relevant file is
reloaded. This allows one to write a huge amount of code in shellscript
without increasing either memory usage or startup time and more
importantly, without forcing the user to turn on specific features
manually. Features that aren't used will not take up memory or
processing power. Fish ships with many thousand lines of shellscript
code, but only a few hundered lines are run on startup.
Dropped features:
Fish tries to consolidate multiple strongly related Posix features into
one. One such consolidation is dropping dollar-quotes like $'\n' in
favor of allowing backslash escapes in regular strings, e.g. \n and
\x20 both work as you would expect. Other dropped features include
subshells (use fish scoping or a block of commands), math mode (use the
bc or expr commands), here documents (Use quotes or, in order to paste
large amounts of data possibly containing quotes and other illegal
characters into the shell, use ^Y to paste from the X clipboard) and
process substitution (Use the psub workalike described above).
Failiures:
While implementing new syntax features for fish, a great many different
ideas where tried out. Most of the ideas that I tried out turned out to
be bad ones, and they where dropped. I have often heard the argument
against chaing the Posix syntax that the original designers knew what
they where doing, and that chaning things will only make them worse.
This is true in the sense that if one would simply try out every gret
new idea one gets, and then make it stick around forever, even if it
turns out that the idea was a bad one, things will degenerate quickly.
But one _can_ to some extend separate the wheat from the chaff. I have
presented some of what I concluded to be wheat above. For those that
are interested, here follows some of the chaff:
Originally, when fish encountered a wildcard that had no matches, it
would silently remove that argument. This makes a huge amount of sense
sometimes, eg. when doing things like 'for i in *.txt', but does not do
what you would like when using e.g. 'ls *.txt'. I do not like the bash
method of leaving such arguments unexpanded, since it relies on the
called commands to detect the wildcard and try to report a meaningful
error. This can lead to very confusing behaviour. Instead, I've opted
to follow the same path chosen by csh, namely if all waldcards to a
specific command fail to match, the command is not executed. In
interactive mode, a warning is printed as well. This is just one of the
many things I feel csh got right. Too bad that the number of things csh
got horribly wrong is even greater...
Originally, fish made no difference between single quotes and double
quotes, they both turned of all types of argument expansion. I argued
that to embed strings into a quoted string, one quold simply use
printf. I aslo rargued that mising the two mixes code with text, which
is bad. I argued that it is confusing to have two types of quotes,
which are similar but not identical. In the end, I allowed variable
expansion in double quotes, because it saves keystrokes, because it
allwos you to make sure array variables get expanded into exactly one
token, no matter how many elements the array contains, and it allows me
get much more time for hacking instead of answering the same question
over and over again.
Originally, fish did not have any blocks at all. Instead, code was
given as arguments to other commands; e.g. the last argument to a for
loop was the list of commands to run in the loop. This is easy to
implement, but completely useless for longer scripts.
Closing comments:
There are a great many features in fish that have little to do with
syntax, like syntax highlighting, advanced tab completion, X clipboard
intergration, etc.. But this post is only mean to discuss the design
and implications of the changes made to regular shell syntax in fish.
Specifically, I'd be interested in opinions on security considerations,
regressions and further possible changes to the syntax. To try out
fish, visit http://roo.no-ip.org/fish/ or use the prepackaged version
avaialable for many systems including Debian. Fish is GPL:ed, and it
works on most Linux versions, NetBSD, FreeBSD, OS X, Solaris and
possibly Cygwin.
--
Axel
Axel