EshellForLoop

Here are two examples of the eshell equivalent of bash for loops:

This in bash:

    for i in *.pm
    do
      rm $i
      cvs remove $i
    done

becomes this in eshell:

    for i in *.pm { rm $i; cvs remove $i }

and this in bash:

    for i in 1 2 3 4; do echo $i; done

becomes this in eshell:

    for i in 1 2 3 4 {echo $i}

and this in bash:

    for i in `find -name Root`; do cp -f ~/src/emacs/CVS/Root $i; done

becomes this in eshell:

    for i in {find -name Root} {cp -f ~/src/emacs/CVS/Root $i}

Extended example

In other shells, we might want to do convert all xpm files to png. This means we need a loop, and we need the basename of a file. In other shells, this is done via backticks:

   ~ $ for f in *.xpm; do convert $f `basename $f .xpm`.png; done

or via parameter expansion:

   ~ $ for f in *.xpm; do convert $f ${f/%.xpm/.png}; done

In eshell, this won’t work:

   for f in *.xpm {convert $f (file-name-sans-extension $f)}

The function ‘file-name-sans-extension’ gets the symbol $f instead the value.

That’s because you’re using lisp for the file-name-sans-extension part. Lisp doesn’t use $name for variables. It uses name. While we’re at it, let’s use the proper $() and ${} formats since () and {} will sometimes fail (For instance, when you add text to the end as we are doing here.):

    for f in *.xpm {convert $f $(file-name-sans-extension f).png}

Another option provided either there is no executable in your path named file-name-sans-extension (which is probably a reasonable bet) or you have enabled eshell-prefer-lisp-functions:

    for f in *.xpm {convert $f ${file-name-sans-extension $f}.png}

You have to write it out in Lisp:

    (mapc (lambda (f)
            (shell-command (format "convert %s %s"
                                   f
                                   (concat (file-name-sans-extension f)
                                           ".png"))))
          (eshell-extended-glob "*.xpm"))

Does anybody know how to do it with less lisp?

How about:

    (dolist (f (eshell-extended-glob "*.xpm"))
       (shell-command (format "convert %s %s.png" f (file-name-sans-extension f))))

Which has the advantage of looking like the original for loop and still being fairly lisp-ish.

– Shaleh

Using modifiers:

   for f in *.xpm(:r) {convert $f.xpm $f.png}

the (:r) removes the extension

This can be done with some other modifiers too, e.g:

   for f in *.xpm *.jpg {convert $f $f(:s/\..*$/.png/)}

I could not get this to work though

   for f in *.xpm *.jpg {convert $f $f(:r).png}

A bit uglier because you need to separate the modifier from the new extension, but this works:

    for f in *.xpm *.jpg {convert $f ${echo $f(:r)}.png}

Alternatively, just keep using modifiers because they’re awesome. A substitute that matches the end of the line ($) is an append:

    for f in *.xpm *.jpg {convert $f $f(:r:s/$/.png/)}

Tried this little example out and it seems that it works if you do not use the dollar-sign. I don’t know why it works, I solved it by trial and error 😊 Maybe someone else can explain and clean up my mess below:

  ~ $ echo 1 > 1.txt; echo 2 > 2.txt; echo 3 > 3.txt
  ~ $ ls
  1.txt  2.txt  3.txt
  ~ $ for f in *.txt {mv $f (concat (file-name-sans-extension f) ".renamed")}
  ~ $ ls
  1.renamed  2.renamed  3.renamed

The code above works because eshell create a let-bound elisp variable called f for the loop variable that is called $f in the shell. Expansion with eshell-parse-command bears this out.

More examples

I just needed to create 1000 dummy files and though that using eshell was a good idea. This is what I came up with:

 $ echo test > original.txt
 $ for i in (let (lst) (dotimes (i 1000) (setq lst (append lst (list i)))) lst) {cp original.txt $i.txt}

Of course I could have made this entirely in lisp, copying and all, but it was good practice… 😊

MaDa

ouch, in a modern ‘normal’ shell:

 $ echo test | tee {1..1000}.txt

Perhaps a DSL for (very) advanced globbing is in order?

PhilJackson

Unless you care about the order the copies are made in, building the list in reverse with (setq lst (cons i lst)) is simpler than using append. Emacs 21 incorporated the cl library’s push macro, and lets you write this (the 3rd parameter to dotimes gives the end result):

 $ for i in (let (lst) (dotimes (i 1000 lst) (push i lst))) {cp original.txt $i.txt}

However, the best way (Emacs 22 or later) has got to be:

 $ for i in (number-sequence 1 1000) {cp original.txt $i.txt}

– PaulWhittaker


CategoryEshell