20

I'm (really) newbie to functional programming (in fact only had contact with it using python) but seems to be a good approach for some list-intensive tasks in a shell environment.

I'd love to do something like this:

$ [ git clone $host/$repo for repo in repo1 repo2 repo3 ]

Is there any Unix shell with these kind of feature? Or maybe some feature to allow easy shell access (commands, env/vars, readline, etc...) from within python (the idea is to use python's interactive interpreter as a replacement to bash).

EDIT:

Maybe a comparative example would clarify. Let's say I have a list composed of dir/file:

$ FILES=( build/project.rpm build/project.src.rpm )

And I want to do a really simple task: copy all files to dist/ AND install it in the system (it's part of a build process):

Using bash:

$ cp ${files[*]} dist/
$ cd dist && rpm -Uvh $(for f in ${files[*]}; do basename $f; done))

Using a "pythonic shell" approach (caution: this is imaginary code):

$ cp [ os.path.join('dist', os.path.basename(file)) for file in FILES ] 'dist'

Can you see the difference ? THAT is what i'm talking about. How can not exits a shell with these kind of stuff build-in yet? It's a real pain to handle lists in shell, even its being a so common task: list of files, list of PIDs, list of everything.

And a really, really, important point: using syntax/tools/features everybody already knows: sh and python.

IPython seams to be on a good direction, but it's bloated: if var name starts with '$', it does this, if '$$' it does that. It's syntax is not "natural", so many rules and "workarounds" ([ ln.upper() for ln in !ls ] --> syntax error)

3

10 Answers 10

12

There's a Scheme Shell that's probably very close to what you're looking for. I haven't used it myself.

UPDATE :

I just installed and tried it myself. It appears that scsh is more of an interactive Scheme interpreter and scripting language than a really useful interactive shell. You can't just type

echo hello

the syntax appears to be

(run (echo hello))

and it took several minutes of Googling just to find that. The first example here is:

gunzip < paper.tex.gz | detex | spell | lpr -Ppulp &

which translates to:

(& (| (gunzip) (detex) (spell) (lpr -Ppulp)) (< paper.tex.gz))

but that doesn't tell you how to run a simple shell command.

This FAQ entry says:

4.6 Can I use scsh as an interactive shell?

Well, technically you can: just run the "scsh" command and you will enter a Scheme 48 session with all scsh functions available. However, this is definitely not suitable for interactive work: there is no command-line editing, no command-line history, no file/function name completion, no terse syntax, etc.

To alleviate these problems, Martin Gasbichler and Eric Knauel have written Commander S, which runs on top of scsh and provides a comfortable interactive environment. One of its novel features is that it can understand the output of many Unix commands, and allows the user to browse and manipulate it in useful ways. More information about Commander S can be found in the paper describing it: http://www.deinprogramm.de/scheme-2005/05-knauel/05-knauel.pdf Instructions about how to obtain and install Commander S are available from the scsh Web site: http://www.scsh.net/resources/commander-s.html

So maybe that's the real answer.

10

In the category of directly answering the question, there is the ES shell which is intended as a functional replacement for Bash and Zsh etc.

Secondly, in the category of helping you write more functional standard shell, consider learning the pipemill technique:

who | while read username 
do
  cat <<EOF | grep $username
nic
mark
norman
keith
EOF
done | while read username
do
  echo "you have an answer on superuser.com" | mail -s "well done" $username
done

The first while loop is a functional keep (pass on only the non-null values that come out of the loop) and the second is an each (map for side effects only).

This is a tremendous boost to fp in shells.

It is possible to express many things in a more fp style in a shell, it just isn't as easy as it might be. It seems that there is not much interest in making better shells, even though we all use them such a lot.

6

The standard Bourne-style shells (sh, bash, ksh, etc.) already let you do:

for repo in repo1 repo2 repo3 ; do git clone $host/$repo ; done

(Note the need for semicolons before do and done.) Additionally, in bash and other shells, if $repo only appears once in the command, you can write:

git clone $host/{repo1,repo2,repo3}
4
  • sure, and I do use it a lot. but what about other stuff, like lambda functions?
    – Caruccio
    Apr 18, 2012 at 18:57
  • 8
    git clone $host/{repo1,repo2,repo3} doesn't do the same thing as the for loop; it invokes git clone once with three arguments. The fact that does essentially the same thing is an artifact of how git clone works; it doesn't necessarily apply to other commands. (Compare echo foo bar baz to echo foo; echo bar; echo baz, for example.) Apr 18, 2012 at 20:41
  • @caruccio you can use FUN=eval 'git clone '"$host"'$0 to define a lambda to use as for repo in repo{1,2,3} ; do $FUN $repo ; done
    – pqnet
    Aug 19, 2014 at 5:21
  • The biggest issue is that the unix tools often have side effects (like writing to files), so you often can't compose them like you'd want to do in a functional language.
    – weberc2
    Jul 28, 2015 at 14:09
3

Shells are necessarily extremely expressive, meaning you achieve lots with fewer lines, tokens, etc.

We usually make languages expressive by designing them for a special purpose, like shells or DSLs like R, maple, etc. And you find relatively little expressiveness in most general purpose languages like C, C++, Java, etc.

Python, Perl, Ruby, etc. are general purpose languages that're more expressive in ways similar to shells, ala duck typing. So DSLs get welded onto them, ala Sage for math. Ain't so great for actual shell commands though.

Scheme is not imho that expressive, even once you build a DLS, due to all the parentheses.

There are however functional languages like Haskell with enormous expressiveness and a great capacity for building DSLs. Interesting effort to build a shell on Haskell are turtle and shelly.

In practice, there is a learning curve with efforts tools due to Haskell powerful but restrictive type system, but they show considerable promise.

2

Scheme Shell, scsh, is really good.

As Keith Thompson notes, it's not useful as an interactive shell (though Commander S looks like an interesting experiment). Instead, it's an excellent programming language for contexts where having all the POSIX bindings is useful -- that includes cases where you want to call other unix applications. A shell script of more than a few dozen lines will always feel like a hack, no matter how neatly you write sh; in contrast there's nothing stopping you writing significant programs using scsh.

scsh isn't very compact (brevity is both the strength and the weakness of sh-family languages), but it's powerful.

Because it's useful and practical for small and large tasks, scsh is incidentally a good way of getting to grips with a Scheme (although, if that happened to be your goal, you might as well go straight to Racket, these days).

The advantages of functional languages aren't just for list-intensive tasks (though because of their history, they tend to favour lists as a data structure) -- it's a really robust way to get programs written, once you drink the right kool-aid.

There is no meaningful sense in which the sh-style shells are functional, and Python is only functional in the marginal sense that it has a lambda function.

1

First, you should be using "${files[@]}" everywhere you have ${files[*]}. Otherwise spaces will mess you up.

The shell is pretty functional already; if you think in terms of text output being lists of lines, then grep is filter, xargs is map, etc. Pipes are very functional.

The shell is admittedly not the friendliest programming environment, but it's much more suited to interactive use than Python. And it has the very nice feature that the API and the UI are identical - learn both at once.

I don't understand why you find cp [ os.path.join('dist', os.path.basename(file)) for file in FILES ] 'dist' preferable to cp "${FILES[@]}" dist. The latter is much less typing.

1

Elvish Shell is an expressive programming language and a versatile interactive shell, combined into one seamless package. It runs on Linux, BSDs, macOS and Windows.

2
  • Welcome to SuperUser! While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. Oct 23, 2022 at 15:37
  • Please read How do I recommend software in my answers? Oct 23, 2022 at 15:51
0

Don't know about it being "functional", but there is also rc, which the scsh paper mentions. It's a predecesor to es.

On Linux Mint (and probably Debian, Ubuntu...) you can try it with

sudo apt-get install rc
man rc
rc
0

Another option is shell-functools, which provides a set of functional-style shell commands.

To accomplish your objective of cloning a sequence of Git repositories, you could use the map command. As implemented, it doesn't accept commands with arguments, so you'll have to create a git-clone script. To clone from repository URLs from a here document:

cat <<EOF | map run git-clone
https://github.com/sharkdp/shell-functools
https://github.com/wryun/es-shell
https://github.com/scheme/scsh
EOF

Alternatively you could use an array:

repos=(
    https://github.com/sharkdp/shell-functools
    https://github.com/wryun/es-shell
    https://github.com/scheme/scsh
)
printf '%s\n' "${repos[@]}" | map run git-clone

To install a git-clone script, you can do the following supposing ~/.local/bin is in your path:

mkdir -p ~/.local/bin &&
    touch ~/.local/bin/git-clone &&
    chmod 700 ~/.local/bin/git-clone &&
    cat <<EOF >~/.local/bin/git-clone
#!/bin/bash
git clone "\$@"
EOF
0

Nushell is another example of an interactive shell based on functional programming.

You must log in to answer this question.