# Linux: Bash von Java aus aufrufen



## Beni (25. Feb 2009)

Hallo

Ich muss von Java aus bash in Ubuntu aufrufen. Damit die Environment-Variablen verfügbar sind (das Zeugs das in .bashrc, .profile, etc geladen wird) muss es eine interaktive-login-shell (?) sein.

Jetzt bekomme ich aber immer einen seltsame Fehlermeldung: "bash: no job control in this shell". Danach läuft zwar alles wie es soll, und die "job control" (würde Dinge wie "ctrl+Z" um einen Job zu versenken erlauben) wird auch garnicht benötigt. Aber mich (und mindestens einen Benutzer) stört diese Meldung.

Ich könnte die Meldung einfach in der Anzeige rausfiltern, aber lieber wüsste ich wie ich sie ganz loswerde... Dr. Google konnte mir zwar sagen was die Meldung bedeutet, aber nicht wie ich sie loswerde.

Jede Hilfe ist willkommen 

Das Program unten erzeugt das Problem, bei mir ist seine Ausgabe:

```
bash: no job control in this shell
kuckuck
besigg@pc-10130:~/workspace/TOS Spielwiese$ besigg@pc-10130:~/workspace/TOS Spielwiese$ logout
exit: 0
```
[highlight=Java]package tos_spielwiese;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.iutputStream;
import java.io.PrintWriter;

public class NoJobControl{
    public static void main( String[] args ) throws Exception{
        ProcessBuilder pb = new ProcessBuilder( "bash", "-l", "-i" );

        Process p = pb.start();

        StreamGobbler errorGobbler = new StreamGobbler( p.getErrorStream(), System.err );
        StreamGobbler outputGobbler = new StreamGobbler( p.getInputStream(), System.out );

        errorGobbler.setPartner( outputGobbler );
        outputGobbler.setPartner( errorGobbler );

        errorGobbler.start();
        outputGobbler.start();

        PrintWriter writer = new PrintWriter( p.getOutputStream() );

        writer.print( "echo kuckuck" );
        writer.print( "\n" );

        writer.print( "exit" );
        writer.print( "\n" );
        writer.flush();

        int exitValue = p.waitFor();

        outputGobbler.join();
        errorGobbler.join();

        System.out.println( "exit: " + exitValue );
    }

    /*
     * 
     * Der restliche Code ab hier transferiert lediglich die Ausgabe der 
     * shell zu System.err/out.
     * 
     */


    /**
     * Used to elect a leader when two gobblers try to process their stream
     * at the same time. The winner processes its stream first, afterwards
     * the looser processes its stream. Leadership can change whenever a 
     * gobbler blocks.
     */
    private static class Leader{
        private int time;

        public synchronized int time(){
            time++;
            return time;
        }
    }

    private static class StreamGobbler{
        private InputStream input;
        private OutputStream output;

        boolean usePrintStream = false;

        private Running running = new Running();
        private Thread thread;

        private StreamGobbler partner;
        private Leader leader;

        public StreamGobbler( InputStream input, OutputStream output ){
            this.input = input;
            this.output = output;
        }

        /**
         * Sets the partner of this gobble, this gobbler will not process its
         * {@link InputStream} at the same time when its partner is reading.
         * @param partner the partner
         */
        public void setPartner( StreamGobbler partner ){
            this.partner = partner;
            leader = partner.leader;
            if( leader == null ){
                leader = new Leader();
            }
        }

        protected void setInput( InputStream input ){
            this.input = input;
        }

        protected void setOutput( OutputStream output ){
            this.output = output;
        }

        public synchronized void start(){
            if( thread != null && thread.isInterrupted() )
                thread = null;

            if( thread == null ){
                thread = new Thread( running );
                thread.start();
            }
        }

        public synchronized void stop(){
            if( thread != null ){
                thread.interrupt();
            }
        }

        public void join(){
            Thread thread = this.thread;
            if( thread != null ){
                while( thread.isAlive() ){
                    try{
                        thread.join();
                    }
                    catch ( InterruptedException e ){
                        e.printStackTrace();
                    }
                }
            }
        }

        /**
         * Called every time when a new byte was transmitted. The default implementation
         * does do nothing.
         * @param value the byte that was transmitted or -1
         */
        protected void piped( int value ){
            // nothing
        }

        /**
         * Tells whether this gobble is currently processing input from its
         * {@link InputStream}.
         * @return <code>true</code> if this gobbler is neither waiting for input
         * nor stopped.
         */
        public synchronized boolean isReading(){
            if( thread == null )
                return false;

            return running.reading;
        }

        private class Running implements Runnable{
            private volatile boolean reading = true;
            private volatile int time = 0;

            public void run(){
                try {
                    if( leader != null ){
                        time = leader.time();
                    }

                    while( !Thread.interrupted() ){
                        int next = 0;

                        if( input.available() > 0 ){
                            next = input.read();
                        }
                        else{
                            reading = false;
                            next = input.read();
                            if( leader != null ){
                                time = leader.time();
                            }
                            reading = true;
                        }

                        if( partner != null ){
                            while( partner.isReading() && time > partner.running.time ){
                                Thread.yield();
                            }
                        }

                        if( next == -1 ){
                            piped( next );
                            synchronized( StreamGobbler.this ){
                                thread = null;
                            }
                            return;
                        }

                        if( output != null ){
                            output.write( next );
                        }
                        piped( next );
                    }
                }
                catch( InterruptedIOException ex ){
                    // ignore
                }
                catch( IOException ioe ){
                    ioe.printStackTrace();
                }
            }
        }
    }
}
[/highlight]


----------



## bygones (25. Feb 2009)

für was brauchst du die konsole ?

rufst du dann ein weiteres Programm auf ? 

und warum stört die ausgabe ?


ins blaue mal rein... 

a) entweder rufst du das programm was du nutzen willst direkt auf
b) schreib dir ein bash script was die befehle bündelt und rufe das über java dann auf....

Gruß
dbc


----------



## Beni (25. Feb 2009)

Mein Programm baut auf anderen, existierenden Tools auf. Der sog. TinyOS-Tool-Chain. Für diese Tool-Chain wird empfohlen, dass man .bashrc modifiziert um sie zum laufen zu bringen. Mit der Modifikation werden verschiedene Pfade als Variablen abgelegt. Grundsätzlich ist TinyOS immer mit viel Gebastel verbunden. Das einzige wobei ich mir sicher sein kann, ist dass die Tool-Chain funktioniert wenn man direkt was in die Konsole eingibt.

Ich benötige bash nun für zwei Dinge:
1. um Pfade zu finden (mit "echo $VARIABLE"). Diese Variablen sind leider nicht in der Map von System.getenv zu finden.
2. Um "make" und eine Variante des "gcc" aufzurufen. Die Dinger hängen wieder von diesen Pfaden ab. Die Ausgabe von make und gcc interessiert den Benutzer, da hier einige wichtige Fehler angezeigt werden könnten... (wenn da gross rot steht, dass irgendwas mit "job" kaputt ist, dürfte der ein oder andere erschrecken)

a) fällt damit wohl aus

b) was ich jetzt mache ist ja schon ziemlich ähnlich wie ein Script, und ein Script müsste man doch auch wieder mit bash aufrufen... was dann beim selben Problem endet?

thx Beni


----------



## Ebenius (25. Feb 2009)

Meine man-Page zu bash behauptet hierzu: 





> When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it  first  reads and  executes commands from the file /etc/profile, if that file exists.  After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists  and  is readable.  The --noprofile option may be used when the shell is started to inhibit this behavior.



Das heißt, Du müsstest Zeile 11 austauschen gegen: [HIGHLIGHT="Java"]ProcessBuilder pb = new ProcessBuilder( "bash", "--login" );[/HIGHLIGHT]

HTH, Ebenius


----------



## Beni (26. Feb 2009)

Ohne "-i" werden die .bashrc-Dateien nicht geladen, dann fehlen wieder die Pfade.

(ich kann nichtmal --rcfile verwenden weil irgendein D*pp diese Variablen sicherlich aus seiner persoenlichen Spezialdatei laedt).


----------



## HoaX (26. Feb 2009)

Kannst ja die Dateien von Hand "nachladen": 

```
source $HOME/.bashrc usw
```
oder

```
. $HOME/bash
```
usw


----------



## Ebenius (26. Feb 2009)

Beni hat gesagt.:


> Ohne "-i" werden die .bashrc-Dateien nicht geladen, dann fehlen wieder die Pfade.
> 
> (ich kann nichtmal --rcfile verwenden weil irgendein D*pp diese Variablen sicherlich aus seiner persoenlichen Spezialdatei laedt).


Was sagt denn die man-Page in Deiner Umgebung dazu? Laut dem Zitat oben würde ich davon ausgehen, dass ... 
	
	
	
	





```
bash --login
```
 ... genügt.

Ebenius


----------



## Beni (26. Feb 2009)

@HoaX
Die Idee ist gut, aber wie kann ich sicher sein, dass das auch in Zukunft und auf anderen Systemen funktioniert? :-/

@Ebenius

Laut man:


> When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option,  it  first  reads  and  executes  commands  from  the  file
> /etc/profile,  if  that file exists.  After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes com‐
> mands from the first one that exists and is readable.  The --noprofile option may be used when the shell is started to inhibit this behavior.
> 
> ...


Wenn ich alle Kombinationen ausprobiere, wird deutlich, dass "-i" immer zum lesen von bashrc führt (das widerspricht dem manual? Vielleicht ein zusaetzliches Script das noch irgendwo versteckt ist.). Im Code unten wurde jeweils "echo $TOSROOT" ausgeführt, was einen Pfad ausgibt der in bashrc definiert ist.


```
>>>>> bash

exit: 0
<<<<<
>>>>> bash --login

exit: 0
<<<<<
>>>>> bash -i
bash: no job control in this shell
/opt/tinyos-2.1.0
besigg@pc-10130:~/workspace/TOS Spielwiese$ besigg@pc-10130:~/workspace/TOS Spielwiese$ exit
exit: 0
<<<<<
>>>>> bash --login -i
bash: no job control in this shell
/opt/tinyos-2.1.0
besigg@pc-10130:~/workspace/TOS Spielwiese$ besigg@pc-10130:~/workspace/TOS Spielwiese$ logout
exit: 0
<<<<<
```
[highlight="Java"]
    public static void main( String[] args ) throws Exception{
        test( "bash" );
        test( "bash", "--login" );
        test( "bash", "-i" );
        test( "bash", "--login", "-i" );
    }

    private static void test( String... command ) throws Exception{
        System.out.print( ">>>>>" );
        for( String cmd : command ){
            System.out.print( " " );
            System.out.print( cmd );
        }
        System.out.println();

        ProcessBuilder pb = new ProcessBuilder( command );
        Process p = pb.start();

        StreamGobbler errorGobbler = new StreamGobbler( p.getErrorStream(), System.err );
        StreamGobbler outputGobbler = new StreamGobbler( p.getInputStream(), System.out );

        errorGobbler.setPartner( outputGobbler );
        outputGobbler.setPartner( errorGobbler );

        errorGobbler.start();
        outputGobbler.start();

        PrintWriter writer = new PrintWriter( p.getOutputStream() );


        // writer.print( "make -C /home/besigg/runtime-Yeti2/Blink3/src mica2" );
        writer.print( "echo $TOSROOT" );
        writer.print( "\n" );

        writer.print( "exit" );
        writer.print( "\n" );
        writer.flush();

        int exitValue = p.waitFor();

        outputGobbler.join();
        errorGobbler.join();

        System.out.println( "exit: " + exitValue );

        Thread.sleep( 1000 );
        System.out.println( "<<<<<" );
    }[/highlight]


----------



## Beni (26. Feb 2009)

Ich glaub ich löse das Problem anders: zuerst lese ich die Variablen im Hintergrund aus, und benutz dann eine nicht-interaktive Shell um make/gcc aufzurufen (bin mir nichtmal sicher, ob ich dann noch eine Shell benötige). Die Variablen muss ich dann zwar von Hand setzen, aber da kann man die Map vom ProcessBuilder verwenden.


----------



## HoaX (26. Feb 2009)

Beni hat gesagt.:


> @HoaX
> Die Idee ist gut, aber wie kann ich sicher sein, dass das auch in Zukunft und auf anderen Systemen funktioniert? :-/



Wieso solltest du dir nicht sicher sein? Die Namen der Initskripte werden sich sicher nicht von heute auf morgen ändern.


----------



## Beni (27. Feb 2009)

HoaX hat gesagt.:


> Wieso solltest du dir nicht sicher sein? Die Namen der Initskripte werden sich sicher nicht von heute auf morgen ändern.



Das stimmt schon, ich finde es nur immer sehr ungemütlich mich bei so einem Problem auf die Dokumentation von 3. Programmen zu verlassen.


----------

