SkillAgentSearch skills...

Shell

A Nim mini DSL to execute shell commands

Install / Use

/learn @Vindaar/Shell
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

  • shell [[https://travis-ci.org/Vindaar/shell][https://travis-ci.org/Vindaar/shell.svg?branch=master]]

A mini Nim DSL to execute shell commands more conveniently.

** Usage With this macro you can simply write #+BEGIN_SRC nim shell: touch foo mv foo bar rm bar #+END_SRC which is then rewritten to something equivalent to: #+BEGIN_SRC nim execShell("touch foo") execShell("mv foo bar") execShell("rm bar") #+END_SRC where =execShell= is a proc around =startProcess= for normal compilation and =gorgeEx= when using NimScript.

Note: When using =NimScript= the given command is prepended by #+BEGIN_SRC &"cd {getCurrentDir()} && " #+END_SRC in order to switch the evaluation into the directory of the =shell= call. The same is achieved on the compiled backend by the =poEvalCommand= argument to =startProcess=.

See [[Full expansion of the macro]] below for more details and how to read the exit code of executed commands.

Most simple things should work as expected. See below for some known quirks.

** ~one~ and ~pipe~

By default each line in the =shell= macro will be handled by a different call to =execShell=. If you need several commands, which depend on the state of the previous, you may do so via the =one= command like so: #+BEGIN_SRC nim shell: one: mkdir foo cd foo touch bar cd ".." rm foo/bar #+END_SRC

Similar to the =one= command, the =pipe= command exists. This concats the command via the shell pipe =|=: #+BEGIN_SRC nim shell: pipe: cat test.txt head -3 #+END_SRC will produce: #+BEGIN_SRC nim execShell("cat test.txt | head -3") #+END_SRC

Both of these can even be combined! #+BEGIN_SRC nim shell: one: mkdir foo pushd foo echo "Hallo\nWorld" > test.txt pipe: cat test.txt grep H popd rm foo/test.txt rmdir foo #+END_SRC will work just as expected, echoing =Hallo= in the shell.

** Handling programs that require user input

Some terminal commands will require user input. Starting from version =v0.5.0=, basic support for a =expect= / =send= feature is available. It allows for functionality similar to the [[https://linux.die.net/man/1/expect][=expect(1)= program]].

Let's consider two simple examples. Assuming we have a script that reads from =stdin= such as:

=tExpect.sh=: #+begin_src sh #!/bin/sh

echo "Hello world. Your name?" read foo echo "Your name is" $foo #+end_src

calling this script using the =shell= macro would normally #+begin_src nim import shell shell: ./tExpect.sh #+end_src cause the nim program to simply stop upon encountering the =read= line.

Now we can do just this: #+begin_src nim shell: ./tExpect.sh send: "BaFu" #+end_src

This goes full rogue mode and just sends "BaFu", regardless what the question is.

You can also specifiy what answer you send to which question. By using =expect= / =send= we can handle it this way:

#+begin_src nim import shell shell: ./tExpect.sh expect: "Your name?" send: "BaFu" #+end_src As you notice the =expect= line only contains the second part of the last printed line of the shell script. That is because the =expect= functionality tries to match the shell output line by line using one of the following:

  • the =expect= line matches exactly
  • the line starts with the =expect= line
  • the line ends with the =expect= line (useful for long outputs that end with ="y/N"= like constructs) (this may become configurable in the future)

Another example would be installing a nimble package and dealing with the possibility of having to upgrade / overwrite package: #+begin_src nim import shell shell: nimble install foo expect: "Overwrite? [y/N]" send: "y" #+end_src which handles such situations programatically.

Note: You may have multiple =expect= / =send= constructs in a single =shell= call. But keep in mind that every =expect= must have a =send=.

** Nim symbol quoting

NOTE: In a previous version this was done via accented quotes =`=. For the old behavior compile with =-d:oldQuote=.

Another important feature to make this library useful is quoting of Nim symbols.

This is handled via parenthesis =()= (if you need to run something in a subshell unfortunately that will have to be done with an explicit string now). Any tree in =()= is subject to quoting. That means if an identifier within =()= is preceded by a =$=, the symbol is unquoted. Note however that for the moment only a single variable may be quoted in each =()=.

The simplest case would be: #+BEGIN_SRC nim let name = "Vindaar" shell: echo Hello from ($name) #+END_SRC which will perform the call: #+BEGIN_SRC nim execShell(&"echo Hello from {name}!") #+END_SRC and after the call to =strformat.&=: #+BEGIN_SRC nim execShell("echo Hello from Vindaar!") #+END_SRC

*** Appending to a Nim identifier

Assuming we have a filename identifier and we want to convert some image from =png= to =jpg= with image magick. The simplest command should look like: #+BEGIN_SRC sh convert myimage.png myimage.jpg #+END_SRC

This can be done in several ways.

**** Using dot expressions and no string literals: #+BEGIN_SRC nim let fname = "myimage" shell: convert ($fname).png ($fname).jpg #+END_SRC Note that this is a special case. Continuing after a =()= quote without literal strings will only work for dot expressions. For instance: #+BEGIN_SRC nim let fname = "myimage" shell: convert ($fname)".png" ($fname)".jpg" #+END_SRC will wrongly be converted to: #+BEGIN_SRC sh convert myimage .png myimage .jpg #+END_SRC which is obviously not what one would expect.

**** Using string literals: #+BEGIN_SRC nim let fname = "myimage" shell: convert ($fname".pdf") ($fname".png") #+END_SRC In contrast to the wrong example shown above, this will work as expected.

This is especially useful for cases without dot expressions after the quoted nim identifier.

*** Appending a Nim identifier to a string literal

The other example would be appending a Nim identifier to a literal string. For instance in case we have a filename, which we create at run time and we wish to hand it to some command which takes an argument, which is must be given without a space like: #+BEGIN_SRC sh ./myBin input --out=output #+END_SRC

In this case one of the following ways works:

**** using =()= after a string literal: #+BEGIN_SRC nim let outfile = "myoutput.txt" shell: ./myBin input "--out="($outfile) #+END_SRC If the =()= appears after the literal we can correctly generate the string without a space (in comparison to the case presented above when a string literal follows a =()=).

**** For more predictable behavior, put the string literal also into =()=: #+BEGIN_SRC nim let outfile = "myoutput.txt" shell: ./myBin input ("--out="$outfile) #+END_SRC

*** General remark on predictability

NOTE: previously this section said to handle quoting + concatenation with strings both in the case of with and without space with =()= for the most predictable behavior. But that was a bad idea from my side! If you need spaces, simply put it outside the =()= and use a space!

The =doAssert= below is to be understood in the context of the =shell= macro. To summarize the above then: #+BEGIN_SRC nim let outfile = "myoutput.txt" doAssert ("--out="$outfile) == &"--out={outfile}" # <- without space, ident after doAssert "--out" ($outfile) == &"--out {outfile}" # <- with space, ident after let fname = "myimage" doAssert ($outfile".jpg") == &"{fname}.jpg" # <- without space, ident first doAssert ($outfile) "image2" == &"{outfile} image2" # <- without space, ident first #+END_SRC

NOTE 2: For the moment however, the =()= usage is restricted to a single string literal (or something that is convertible to a string via the =stringify= proc) and a single Nim identifier! This restriction will maybe be removed in the future.

This syntax also works for more complicated Nim expressions than a simple identifier: #+BEGIN_SRC nim const t = (a: "name", b: 5.5) doAssert ("--out="$(t.a)) doAssert ("--out="$t.a) #+END_SRC both work. Of course =t= needn't be a tuple. It can also be an object or even a function call, like for instance extracting a filename within a call: #+BEGIN_SRC nim import os, shell let path = "/some/user/path/toAFile.txt" shell: ./myBin ("--inputFile="$(path.extractFilename)) #+END_SRC should produce: #+BEGIN_SRC sh ./myBin --inputFile=toAFile.txt #+END_SRC

** Accented quotes

NOTE: In a previous version accented quotes were also used to quote Nim identifiers. That use case is now handled via parentheses. For the old behavior compile with =-d:oldQuote=.

Accented quotes allow you to hand raw strings.

Note: this has the downside of disallowing == as a token to be handed to the shell. If you want to use the shell's ==, you need to put the appropriate command into quotation marks.

*** Raw strings If you want to hand a literal string to the shell, you may do so by putting it into accented quotes: #+BEGIN_SRC nim echo hello #+END_SRC will be rewritten to #+BEGIN_SRC nim execShell("echo "hello"") #+END_SRC

For a string consisting of multiple commands / words, put quotation marks around it: #+BEGIN_SRC sh echo "Hello from Nim!" #+END_SRC which will then also be rewritten to: #+BEGIN_SRC nim execShell("echo "Hello from Nim!"") #+END_SRC

** Assignment of results to Nim variables

Also useful is assignment of the result of a shell call to a Nim string. This can be done with the =shellAssign= macro. It is a little special compared to the =shell= and =shellEcho= macros. It only supports a single statement (*), which needs to be an assignment of a shell call of the syntax presented above to a Nim variable, such as: #+BEGIN_SRC nim var name = "" shellAssign: name = echo Araq assert name == "Araq" #+END_SRC Here the left =name= is the Nim variable (note: this is an exception of the Nim symbol quoting mentioned above!), whereas the right hand side is an arbitrary shell call, in this case a simple call to =echo=. The Nim variable will be assigned the result of the shell call, by being

View on GitHub
GitHub Stars160
CategoryDevelopment
Updated5d ago
Forks7

Languages

Nim

Security Score

80/100

Audited on Mar 21, 2026

No findings