0

I am trying to write a program that runs the Linux commands using Scala.

I have written a snippet of code to run the functionalities of md5sum command.

Code snippet

object Test extends App {
  import sys.process._

  case class md5sum_builder private(i: Seq[String]) {
    println(i)
    protected def this() = this(Seq(""))
    def optionCheck() = new md5sum_builder(i :+ "-c")
    def files(file: String) = new md5sum_builder(i :+ file)
    def hashFile(hashfile: String) = new md5sum_builder(i :+ hashfile)
    def assignment(operator: String) = new md5sum_builder(i :+ operator)
    def build() = println(("md5sum" + i.mkString(" ")).!!)
  }

  object md5sum_builder {
    def apply() = new md5sum_builder
  }

  md5sum_builder().files("text.txt").files("text1.txt").assignment(">").hashFile("hashes.md5").build()

}

When I try to run the command md5sum text.txt text1.txt > hashes.md5 using this program, it throws the error:

Error: md5sum: stat '>': No such file or directory

I don't know why. Any way to make it work?

sachinmb27
  • 29
  • 8
  • 2
    Piping via `>` is something that is provided by a shell (like bash). If you want to use that, you cannot exec `md5sum` directly, you have to ask a shell to do that. – Thilo Nov 24 '19 at 01:28
  • 1
    But why call an external process at all? You can easily do MD5 operations in Scala or Java itself. https://stackoverflow.com/q/415953/14955 – Thilo Nov 24 '19 at 01:29
  • Does this answer your question? [Using Java ProcessBuilder to Execute a Piped Command](https://stackoverflow.com/questions/3776195/using-java-processbuilder-to-execute-a-piped-command) – Thilo Nov 24 '19 at 01:29
  • I was asked to create a framework for executing external linux commands using Scala. So, I need to do it in this way @Thilo – sachinmb27 Nov 24 '19 at 01:39
  • 2
    If "external linux command" includes piping then go via a shell as in https://stackoverflow.com/a/3776277/14955 – Thilo Nov 24 '19 at 01:41
  • So, is there any other way that I can save the hashes found for the files in another file without using the '>' operator? @Thilo – sachinmb27 Nov 24 '19 at 01:43
  • 1
    If you are asked to create a framework for executing arbitrary commands you cannot start thinking about alternatives to the provided commands. You would not even know that `> outputfile` is part of that command. If it is within the scope of your assignment to capture the output of the given command and write it to a file of your choosing, you could do that. Leave the `> outputfile` part off, then output will be written to stdout and you can accept that into a Scala string. https://stackoverflow.com/q/16714127/14955 – Thilo Nov 24 '19 at 01:51
  • Note that the " -c" part might fail. In the shell, program and parameters are separated with (at least one) whitespace. But if we call them with a collection of strings programmatically, they are already separated. The shell did the parsing and word splitting and is handing distinct words to the program, superfluous blanks already removed. But with your p., md5sum will not receive the string "-c" as expected, but with a leading blank in front - and md5sum will most probably not trim the input before analyzing. You may test it in the shell by masking the blank. – user unknown Nov 24 '19 at 05:42
  • Thank you @userunknown, corrected it. – sachinmb27 Nov 24 '19 at 19:58

1 Answers1

1

Your interface doesn't appear to be well thought out. Notice that files(), hashFile(), and assignment() all do the same thing. So someone could come along and do something like this ...

md5sum_builder().assignment("text0.txt")
                .hashFile("text1.txt")
                .files(">")                // <--shell redirection won't work
                .assignment("hashes.md5")
                .build()

... and get the same (non-functional) result as your posted example.

Here's a modification that corrects for that as well as allowing redirected output.

case class md5sum_builder private(i :Seq[String], outfile :String = "/dev/null") {
  protected def this()          = this(Seq.empty[String])
  def optionCheck(file :String) = this.copy(i = i ++ Seq("-c", file))
  def file(file: String)        = this.copy(i = i :+ file)
  def hashFile(file: String)    = this.copy(outfile = file)
  def build() = println(("md5sum" +: i).#|(Seq("tee", outfile)).!!)
}

Now the methods can be in almost any order and still get the expected results.

md5sum_builder().file("text0.txt")
                .hashFile("hashes.md5")
                .file("text1.txt")
                .build()
jwvh
  • 50,871
  • 7
  • 38
  • 64
  • Thank you so much @jwvh, I did not realize that mistake. Can you explain what this does? `.#|(Seq("tee", outfile))` – sachinmb27 Nov 24 '19 at 20:03
  • `#|` is the `ProcessBuilder` pipe method and `tee` is the Linux tee command (`/usr/bin/tee` on my system). So you can think of it as: `md5sum | tee $outfile` – jwvh Nov 25 '19 at 01:06