0

I am not sure whether to post this on StackOverflow or AskUbuntu, based on my current understanding the problem lies in my lack of understanding of rights and execution permissions/handling within Ubuntu instead of Java programming approach, hence I thought this was the place to ask.

While succesfully executing terminal commands generated in a java project that is compiled to a commandUbuntu.jar, I found a command that does not execute as I thought it would. I installed taskwarrior 2.5 and I am trying to create a custom user defined attribute (UDA) using Java. The command(s) is/are:

printf 'y\n' | sudo task config uda.newTestSort.type numeric
printf 'y\n' | sudo task config uda.newTestSort.label nTSort

reproduction Taskwarrior can be installed with:

sudo apt update && sudo apt upgrade
sudo apt install task

You can add 2 tasks with:

task add this is the test task one description
task add test task two

Attached is a MWE created to just run the 2 commands, it can be compiled to .jar in eclipse by clicking: file>export>select node Java>Select Runnable JAR file>Click next>Launch Configuration:CommandLinux - LearnToSayYesToLinux>Chose an export location and name, e.g.c:/commandLinux.jar`>Package required libraries into generated JAR>Click Finish.

Then it can be run in Ubuntu with:

cd /mnt/c/commandLinux.jar
java -jar commandLinux.jar

MWE:

package learnToSayYesToLinux;

import java.io.*;
import java.util.ArrayList;


public class CommandLinux {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //Test create a custom UDA
        createUDA("abstractSort","aSort","numeric");

        System.exit(0);
    }

    /**
     * Method creates a taskwarrior user defined Attribute if the data type is correct
     * Thows error datatype is not correct.
     * TODO: write proper exception
     * @param udaName
     * @param label
     * @param type
     */
    private static void createUDA(String udaName, String label,String type) {
        char vd = (char)124; //vertical dash: |
        char bs = (char)92; //backslash: \      
        String[] commands = new String[2];

        //Check if the datatype is correct for taskwarrior:
        if (type.equals("numeric") || type.equals("string") || type.equals("date") || type.equals("duration")){
            commands[0]="printf 'y"+bs+"n' "+vd+" sudo task config uda."+udaName+".type "+type;
            commands[1]="printf 'y"+bs+"n' "+vd+" sudo task config uda."+udaName+".label "+ label;          

            runCommands(commands[0], false);
            runCommands(commands[1], false);

            System.out.println("Ran:"+commands[0]);
            System.out.println("Ran:"+commands[1]);
            //Trow exception if the datatype is not correct.
        }else {
            try {
                throw new Exception();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }


    public static ArrayList<ArrayList<String>> runCommands(String command,boolean ignoreOutput) {

        String s = null;
        String outputLines=null;
        ArrayList<String> goodExecutionOutput=new ArrayList<String>();
        ArrayList<String> errorExecutionOutput=new ArrayList<String>();
        ArrayList<ArrayList<String>> returnLists = new ArrayList<ArrayList<String>>();

        try {
            // run the Unix "task nice0" command
            Process p = Runtime.getRuntime().exec(command);
            BufferedReader brGood = new BufferedReader(new InputStreamReader(p.getInputStream()));
            BufferedReader brError = new BufferedReader(new InputStreamReader(p.getErrorStream()));

            // get output
            if (!ignoreOutput) {
                while ((s = brGood.readLine()) != null) {
                    System.out.println("Adding:"+s);
                    goodExecutionOutput.add(s);
                }

                // get the error message
                while ((s = brError.readLine()) != null) {
                    errorExecutionOutput.add(s);
                }   
            }

        }
        catch (IOException e) {
            System.out.println("Error: ");
            e.printStackTrace();
            System.exit(-1);
        }

        //Merge outputLists and return
        returnLists.add(goodExecutionOutput);
        returnLists.add(errorExecutionOutput);
        return returnLists;
    }   
}

Result If the command is executed by the .jar file it returns:

ationAndSystems/Taskwarrior/customSortServerV4$ java -jar testCommand.jar

Adding:'y
Adding:'
Adding:'y
Adding:'
Ran:printf 'y\n' | sudo task config uda.newTestSort.type numeric
Ran:printf 'y\n' | sudo task config uda.newTestSort.label nTSort

If I run the command manually it returns:

$ printf 'y\n' | sudo task config uda.newTestSort.type numeric
Are you sure you want to change the value of 'uda.newTestSort.type' from 'numeric' to 'numeric'? (yes/no) Config file /home/a/.taskrc modified.
$ printf 'y\n' | sudo task config uda.newTestSort.label nTSort
Are you sure you want to change the value of 'uda.newTestSort.label' from 'nTSort' to 'nTSort'? (yes/no) Config file /home/a/.taskrc modified.

The interpretation/testing of this result is discussed at "Verification".

Result attempt 2 As suggested in the comments: Modifying lines 36 and 37 of the MWE to:

commands[0]="yes yes "+vd+" sudo task config uda."+udaName+".type "+type;
commands[1]="yes yes "+vd+" sudo task config uda."+udaName+".label "+ label;

Yields an infinite repetition (until Stack Overflow occurs) of:

Adding:yes | sudo task config uda.testSortA.type numeric

If I set the boolean ignoreOutput to false when I call method runCommands() it returns:

Ran:yes yes | sudo task config uda.testSortA.type numeric
Ran:yes yes | sudo task config uda.testSortA.label tSortA

After verification I concluded this command did not effectively add a new UDA.

Verification If I manually enter them in Ubuntu 16.04 they work fine. I test it by entering:

sudo task 2 modify newTestSort:29

If the UDA does not yet exist in taskwarrior it will interpret that statement as: "modify the description of task 2". However if the UDA newTestSort does exist and is of type numeric it will set the task 2 uda newTestSort (labelled with nTSort) to 2. These two results are read in the output of command sudo task 2, first before creation of the UDA to verify the UDA does not yet exist by checking if the task description has been changed to newTestSort:29 and a second time, after entering the 2 UDA creation commands listed above, to verify the UDA is indeed created, by checking if the new UDA nTSortis listed with value 29.

Question: How do I automatically answer "yes" to the prompt below without creating an additional file, using Java?

Are you sure you want to change the value of 'uda.newTestSort.type' from 'numeric' to 'numeric'? (yes/no) Config file /home/a/.taskrc modified.

Current understanding The answer by lesmana explains that the vertical dash connects the input for what is right of the dash with the output of what is left of the dash. It says the output of yes is an endless stream of y's, so afther the command asks for input it would stream those y-s into that question. I currently do not understand why it would stop doing that.

Another doubt I have is that the command might be interpreted differently compared to when it is manually entered due to the way it is given from a .jar file to a process instead of entered in terminal. This might cause for a different interpretation/meaning of the |. I am looking into how the commands are interpreted from a .jar file by ubuntu.

Additionally I checked whether it was a problem with the rights that the .jar file has, so I tried to execute the suggested command of attempt 2 with:

sudo java -jar commandUbuntu.jar

But after verification I concluded that that did not allow for adding a new UDA from Java.

7
  • 3
    as the questions asks for yes/no and not for y/n you should use yes yes to print endless yes and not y. Anyways, you should clarify what happens when you use printf 'y\n' | ... or yes | ...
    – pLumo
    Feb 8, 2019 at 13:29
  • Thank you for your suggestion @RoVo, can you explain why a double yes yes is required to print an endless stream of yes, and not for example a single yes, or triple yes? I will try it and include the results of both my described attempt and of your suggestion.
    – a.t.
    Feb 8, 2019 at 13:42
  • the arguments to yes defines its output. Default is y. See the manual: man yes --> Repeatedly output a line with all specified STRING(s), or 'y'..
    – pLumo
    Feb 8, 2019 at 14:42
  • 1
    Regarding your attempt2: Instead of printf 'yes yes "+bs+"n', use just yes yes
    – pLumo
    Feb 8, 2019 at 14:44
  • I included your 2nd explicit suggestion and tested it. It did not add a new UDA. I assumed the vertical dash should still be positioned after one space after the last yes followed by a space followed by the command.
    – a.t.
    Feb 8, 2019 at 15:15

1 Answer 1

0

A solution to the essence of the problem was posted by Pepe below the accepted answer at: https://stackoverflow.com/questions/4157303/how-to-execute-cmd-commands-via-java

With my current understanding, a command can have an input and output. The vertical dash | is used for piping in terminal. So for example suppose you have a command that generates an output, for example yes, then that output can be linked directly to the input of another command*. Suppose that other command is the command I tried to execute which has an input due to the question:

Are you sure you want to change the value of 'uda.newTestSort.type' from 'numeric' to 'numeric'? (yes/no) Config file /home/a/.taskrc modified.

So when for example:

printf 'yes\n' | task config uda.testSortC.type numeric

is typed as a command in normal terminal, it the output of command yes is piped into command: task config uda.testSortC.type numeric, which answers the question above. I currently understand that that question itself is the output of that command, but merely a message that accompanies the input of that command. Receiving input yes terminates the output flow of infinite yes's and the command is executed as it should with a yes input.

Now when you can call a process in Java with:

Process p = Runtime.getRuntime().exec(command);

the .exec(command) is not exactly the same as typing a command into Terminal^. One property of the difference is that piping does not function if it is stated inside the command that you wrote in Java using the vertical line |. To apply the concept of piping you need to explicitly get the input of a command, and explicitly plug the output of another command into it. This can be done with the following code in method runCommands():

Process p;
try {
    p = Runtime.getRuntime().exec(command);
    new Thread(new SyncPipe(p.getErrorStream(), System.err)).start();
    new Thread(new SyncPipe(p.getInputStream(), System.out)).start();
    PrintWriter stdin = new PrintWriter(p.getOutputStream());
    stdin.println("yes");
    // write any other commands you want here
    stdin.close();
    int returnCode = p.waitFor();
    System.out.println("Return code = " + returnCode);

} catch (IOException | InterruptedException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
}

And by adding the following class to your project:

class SyncPipe implements Runnable
{
public SyncPipe(InputStream istrm, OutputStream ostrm) {
      istrm_ = istrm;
      ostrm_ = ostrm;
  }
  public void run() {
      try
      {
          final byte[] buffer = new byte[1024];
          for (int length = 0; (length = istrm_.read(buffer)) != -1; )
          {
              ostrm_.write(buffer, 0, length);
          }
      }
      catch (Exception e)
      {
          e.printStackTrace();
      }
  }
  private final OutputStream ostrm_;
  private final InputStream istrm_;
}

Where, as far as I currently understand, p contains the "gets" the process that executes the command, then the class SyncPipe actually is a pipe. The input of the process p is connected to the pipe using a thread and then a new command that generates an output is executed by stdin. Somehow, the pipe then gets that output stream and plugs it into the input stream of the first command. This solution is made as explicit as possible to force myself to get the concepts clear, as such it might still contain errors, if so, please comment/edit.

*(if that command has an input. I currently do not know whether all commands have inputs, or if not, I currently do not know what happends if an infinite output stream of for example yes is piped into a command without input)

^I currently do not exactly know what the difference is, it requires me to have a deeper understanding of what happends in terminal and how commands are handled and how processes are executed.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .