Why I get no output when use (sh "top") in clojure? - clojure

I try to get output from sh command but don't see any.
(:use [clojure.java.shell :only [sh]])
(sh "ls" "-aul"); <-- WORKS
=>
{:exit 0,
:out "total 136
-rw-r--r-- 1 snaggs staff 113 Jan 8 14:17 .babelrc
drwxr-xr-x 15 snaggs staff 510 Jan 12 14:14 .hg
The command (sh "top") - no output. I know that top command is like listener. So how can I configure sh to get output from top. Suppose tail -f should be the same behavior.
Thanks,

First make sure, that you run your version of top in "batch mode", which just prints to stdout ad infinitum. For Linux and FreeBSD this is top -b. Next sh can not be used for this problem, as it waits for the command to finish. So the same can be done via the ProcessBuilder from Java. E.g.
(require '[clojure.java.io :as io])
; start `top -b`
(let [process (-> (ProcessBuilder. ["top" "-b"]) .start)]
; get the stdout of the process
(with-open [stdout (io/reader (.getInputStream process))]
(loop []
; read a line, handle it, until EOF
(when-let [line (.readLine stdout)]
(println line) ; work here
(recur)))))
(note this lacks a final (.destroy process) call)
Original answer why (sh "top") does not return
Not strictly a Clojure problem. You have to start top with the following params for the linux top: -n1 (only one iteration, then stop) and -b (batchmode, just write to stdout). Other versions might differ here.
user=> (use '[clojure.java.shell :only [sh]])
nil
user=> (sh "top" "-n1" "-b")
{:err "",
:exit 0,
:out "top - 14:06:47 up 126 days, 5:21, 5 users, load average: 0.93, 0.67, 1.12 ,,,
In your example, you just ran top as with you would do from the command line. So it starts and runs waiting for you to quit or kill it - blocking your REPL.

Related

Exporting environmental variables in Clojure / Babashka

I am trying to write an install script in babaska for my other babaska script (for some fun homogeneity).
Export doesn't seem to work using:
(shell "export NAME=" value)
Is there a canonical way to set environmental variables in Clojure / Babashka?
export is shell built-in; you can not call it outside a shell. So
what you want to do instead is run your command with another
environment. This can be done in sh with the :env
option. E.g.
(-> (shell/sh "sh" "-c" "echo $X" :env {"X" "Hello"}) :out)
edit: global state for environment
At least on the JVM, there is no easy way to change the
environment.
So you are better off, writing your own function to do the calls and
merge with your own global environment.
This example uses an atom to keep the environment around:
(def sh-env (atom {}))
(defn export!
[m]
(swap! sh-env merge m))
(defn sh
([cmd]
(sh cmd {}))
([cmd env]
(apply shell/sh (concat cmd [:env (merge #sh-env env)]))))
;;;
(def echo ["sh" "-c" "echo $X"])
(prn (sh echo))
(export! {"X" "Hello"})
(prn (sh echo))
(prn (sh echo {"X" "Goodbye"}))
(export! {"X" "Goodbye"})
(prn (sh echo))
; {:exit 0, :out "\n", :err ""}
; {:exit 0, :out "Hello\n", :err ""}
; {:exit 0, :out "Goodbye\n", :err ""}
; {:exit 0, :out "Goodbye\n", :err ""}
They way to set environment variables with shell is like this:
(shell {:extra-env {"NAME" "FOOBAR"}} command)
See docs here.

A Clojure project building several related small tools

I'm working on a Clojure project with a common library of functionality, but I want to use this library from a number of small command-line tools.
Let's call them tool1, tool2, tool3.
Each of these tools wraps the bulk of the library.
How should I organize my Clojure source code and what do I need in my project.clj file?
Right now my code is in
src/projectname/core.cljc ; a default executable
cljc/projectname/lib1.cljc
cljc/projectname/lib2.cljc ; cljc because I want to use from clojurescript too later
etc.
Where should I put the tool1.clj, tool2.clj etc?
My defproject is
(defproject
....
:aot [projectname.core]
:main project.core )
What do I need to put in to tell it that I want to make the three stand-alone separate, executable tools?
You can do this using the lein-exec library.
First, set up ~/bin like this (or similar):
> ls -ldF ~/bin/l*
lrwxrwxrwx 1 alan alan 26 Oct 31 08:29 /home/alan/bin/lein -> /home/alan/cool/tools/lein*
lrwxrwxrwx 1 alan alan 31 Oct 31 08:29 /home/alan/bin/lein-exec -> /home/alan/cool/tools/lein-exec*
lrwxrwxrwx 1 alan alan 33 Oct 31 08:29 /home/alan/bin/lein-exec-p -> /home/alan/cool/tools/lein-exec-p*
I have symlinks to the actual files, but you can copy them directly. Make sure ~/bin is in your path, of course.
Then just write an executable clojure "script" like the following. Of course, it must be executable:
> ls -l say-hello
-rwxrwxr-x 1 alan alan 212 Nov 2 09:10 say-hello
> cat say-hello
#!/usr/bin/env lein-exec
(defn say-hello [name]
(println (format "Hello from the command line, %s!" name)))
(do
; *command-line-args* = <cmd> <arg1> <arg2> ...
(say-hello (second *command-line-args*)))
And off we go!
> ./say-hello buckaroo
Hello from the command line, buckaroo!
As for project organization, I would start simple (as with any project!). Maybe start off with just 1 source file, with different functions as entrypoints for each "script". As the project grows, it'll be easier to see where you would like to break out different namespaces/files.
Update
You can also do this with lein uberjar, and then invoking java directly:
(ns clj.core
(:gen-class))
(defn say-hello [name]
(println (format "Hello from the command line, %s!" name)))
(defn -main [& args]
(say-hello (first args)))
> lein uberjar
Compiling 1 source files to /home/alan/clj/target/uberjar/classes
Compiling clj.core
Created /home/alan/clj/target/uberjar/clj-0.1.0-SNAPSHOT.jar
Created /home/alan/clj/target/uberjar/clj-0.1.0-SNAPSHOT-standalone.jar
> java -jar /home/alan/clj/target/uberjar/clj-0.1.0-SNAPSHOT-standalone.jar pardner
Hello from the command line, pardner!
So just put the java command into a shell script to kick things off. Also note that :gen-class is required and that args doesn't include the script name anymore. Of course, you must deploy both the script files and the uberjar.
Update #2
You can also use this version:
Source:
(ns clj.core
(:gen-class))
(defn say-howdy [args]
(println (format "Howdy, %s!" (first args))))
(defn give-reply [args]
(println (format "Back at ya, %s!" (first args))))
(defn -main [& args]
(let [method-name (first args)
message (second args) ]
(cond
(= method-name "say-howdy" ) (say-howdy (rest args))
(= method-name "give-reply") (give-reply (rest args))
:else (throw (NoSuchMethodException. (str "clj.core: invalid method='" method-name \')))))
)
scripts:
> ls -l say*
-rwxrwxr-x 1 alan alan 212 Nov 2 16:24 say-hello
-rwxrwxr-x 1 alan alan 104 Nov 2 16:35 say-howdy
-rwxrwxr-x 1 alan alan 105 Nov 2 16:35 say-reply
> cat ./say-howdy
#!/bin/bash
java -jar /home/alan/clj/target/uberjar/clj-0.1.0-SNAPSHOT-standalone.jar say-howdy pardner
> cat ./say-reply
#!/bin/bash
java -jar /home/alan/clj/target/uberjar/clj-0.1.0-SNAPSHOT-standalone.jar give-reply $1
Run tools:
~/clj > ./say-howdy
Howdy, pardner!
~/clj > ./say-reply
Back at ya, null!
~/clj > ./say-reply buckaroo
Back at ya, buckaroo!
Making the scripts smarter and deciding how to deploy the N scripts and single JAR file is left as an exercise for the reader.
;)

Light Table read input

I'm having some trouble to get started with Light Table.
Here's my code (Clojure)
(ns prova1-ed.core
(:gen-class))
(use 'clojure.java.io)
(defn -main [& args]
(println "Type the name of the file to read: ")
(let [fileName (read-line)]
(let [rdr (reader fileName)]
(doseq [line (line-seq rdr)]
(println line)
)))
)
I'm sure it works. I've tested with lein run. As you can see, the program should read a file which the name is given by the user.
I've tried CTRL+SPACE in Light Table, but this is what I receive:
ERROR: Unhandled REPL handler exception processing message {:data {:auto? false, :pos {:line 14, :ch 1}, :mime "text/x-clojure", :tags [:editor.clj :editor.clojure], :type-name "Clojure", :line-ending "\r\n", :ns user, :path "C:\\Users\\Tiago\\Documents\\Clojure\\prova1_ed\\src\\prova1_ed\\core.clj", :print-length nil, :name "core.clj", :local true, :code "(ns prova1-ed.core\n (:gen-class))\n\n(use 'clojure.java.io)\n\n(defn -main [& args]\n\n (println \"Type the name of the file to read: \")\n\n (let [fileName (read-line)]\n (let [rdr (reader fileName)]\n (doseq [line (line-seq rdr)]\n (println line)\n )))\n)\n"}, :id 90, :op editor.eval.clj.sonar, :session 65d1da68-a730-4ffe-9365-9527726384e3}
How can i run it in the Light Tables' enviroment, so that I can input the file name?
TLDR
I don't think you can run (read-line) in Light Table as it'd have to add explicit support for allowing input. There's no standard input basically.
An Alternative
I'd suggest you modify your -main function to accept an explicit file-name argument instead of trying to read it from a standard input that isn't available.
I've got a Clojure webapp that I work on in Light Table.
I've got a -main function in a namespace named my-app.web. It looks something like this:
(defn -main [& [port]]
(let [port (Integer. (or port (env :port) 5000))
store (cookie/cookie-store {:key (env :session-secret)})]
(jetty/run-jetty (-> #'secured-app
wrap-with-logging
wrap-current-user
wrap-current-auth
wrap-error-page
(site {:session {:store store}}))
{:port port :join? false})))
In a separate file I've named light-table-start.clj, I've got the following code to run my app inside Light Table:
(require '[my-app.web :as web])
(require '[ring.adapter.jetty :as jetty])
(defonce server (web/-main "5000"))
;; (.start server)
;; (.stop server)
I run the Eval: Eval editor contents command (Ctrl+Shift+Enter on Windows and Linux or ⌘+Shift+Enter on Mac OS) the first time I want to run my app (or later, if the connection is closed for some reason). When I want to start or stop the server I can just highlight the code on the respective commented lines and run the Eval: Eval a form in editor command (Ctrl+Enter on Windows and Linux or ⌘+Enter on Mac OS).

clojure, freeze after call future and same

I have test script
(defn foo [] ( print "OK!" ))
(print "main")
(future-call foo)
(print "end")
When I run it in REPL, always fine
user=> (defn foo [] ( print "OK!" ))
#'user/foo
user=> (print "main")
mainnil
user=> (future-call foo)
OK!#<core$future_call$reify__6320#1d4997: nil>
user=> (print "end")
endnil
But when I run it from console, I have strange freeze after the code has finished executing
$ time clojure-1.6 /tmp/1.clj
mainend
real 1m1.672s
user 0m2.229s
sys 0m0.143s
mainend displayed almost immediately, but returns to the shell takes about a minute.
pmap also work strange
(defn foo [x] ( print x ))
(print "main")
(pmap foo [1 2 3 4 5 6 7 8 9 0])
(print "end")
will displayed
$ time clojure-1.6 /tmp/1.clj
main12365409end
real 1m1.688s
user 0m2.320s
sys 0m0.114s
I known that ..365.. it's normal for concurrency code, but why 7 and 8 not displayed?
You need to call shutdown-agents
Note: If you leave out the call to (shutdown-agents), the program will on most (all?) OS/JVM combinations "hang" for 1 minute before the process exits. It is waiting for a thread created by the future call to be shut down. shutdown-agents will shut them down immediately, or (System/exit ) will exit immediately without waiting for them to shut down.
This wait occurs even if you use futures indirectly through some other Clojure functions that use them internally, such as pmap or clojure.java.shell/sh
From https://clojuredocs.org/clojure.core/future

How can I get the Ganymed SSH library to tail a file (in Clojure)?

The following code never manages to tail a file. It simply hangs waiting for reader input. Has anyone tried anything similar?
(def output (ref [] ))
(import 'ch.ethz.ssh2.Connection)
(import 'ch.ethz.ssh2.Session)
(import 'ch.ethz.ssh2.StreamGobbler)
(import 'java.lang.StringBuilder)
(import 'java.io.InputStream)
(import 'java.io.BufferedReader)
(import 'java.io.InputStreamReader)
(let [connection (new Connection "hostname")]
(. connection connect)
(let [ok (. connection authenticateWithPassword "username" "password" )
session (. connection openSession )]
(. session execCommand "tail -f filename.txt")
(let [sb (StringBuilder.)
stdout (StreamGobbler. (. session getStdout))
br (BufferedReader. (InputStreamReader. stdout))
]
(future (loop [line2 (. br readLine)]
(if (= line2 nil)
nil
(do
(dosync (ref-set output (conj #output line2)))
(recur (. br readLine))))
)
)
)
)
)
I agree with Arthur. Its not clear to me how this would work in practice since the remote command will never return / finish. Try the following as an example:
> (defn never-returns []
(while true
(do (Thread/sleep 2000)
(println "i'm never going to finish")))
"done")
> (def x (future (never-returns)))
> (#x)
i'm never going to finish
i'm never going to finish
...
I think a possibly better approach would to take the threading out your client while providing a means to get access to the tail of the file asynchronously. For example, you might consider using netcat to send the output of tail -f to a socket and then periodically read from that socket in your client to get the file output. Something like this on the remote side:
tail -f filename.txt | nc -l 12000
Then in your clojure code:
(import '(java.net ServerSocket Socket))
(def client (java.net.Socket. "hostname" 12000))
(def r (java.io.BufferedReader. (java.io.InputStreamReader. (.getInputStream client))))
(println (.readLine r)) ; or capture the output
Something like that. Hope this helps.
I'm not sure a future is the best construct for starting your work thread because it blocks on deref until the work is finished