LinuxQuestions.org
Share your knowledge at the LQ Wiki.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Blogs > rainbowsally
User Name
Password

Notices


Rate this Entry

More Mad Computer Science? (Simple multi-threading in a shell script)

Posted 01-20-2012 at 09:02 AM by rainbowsally

Just dropping by? Here's an easy one. And it's kind of fun.

We've already created a launch utility in this blog, but let's create one that works on other folks systems so they don't need to "install" (so to speak) anything to get the benefits of the functionality.

First let's look at the principles involved

Let's read fstab as a normal user. Don't 'su' or 'sudo su' here, because we don't actually want to be able to write to it. Just picking on that file because everyone has it.

Compare
Code:
kshell4 kwrite /etc/fstab
to
Code:
nohup kwrite /etc/fstab >/dev/null 2>&1 &
[Use 'kate -n' instead of kwrite if you like. For goodness sakes even use gedit. :-) ]

The advantage in using the second version is that it will work for other window managers besides kde. It's straight Linux and the effect is identical for all practical purposes.

The disadvantage is that it's a bit more code to write. And for that alone the kde version is worth being aware of. The 14K (file size) has already been committed and if you KNOW your recipients have kde, you can easily use that instead.

And either one can be incorporated in a newly created launcher for an application which will, in essence, run another application in a new thread, disconnected from the app that launched it.
WARNING: This CAN be dangerous in that if you have an endless loop in the "launched" application, especially if that application was set off as superuser and you have closed the terminal or you run it from a clickable app) because in a single-core system, that loop can hog so much cpu time that you probably won't be able to close it any time real soon. Time for Ye Olde Reset Button, know what I mean?

This can happen in any app, of course, but with a terminal and with the usual stdio connections you can hit Ctrl-C to stop the madness. In this arrangement Ctrl-C has no effect.

So make sure you test your code before sharing it with your friends.
That should suffice for the common sense and the warning. :-)

Now...

Let's say we want to create an application that shows a popup for a few seconds but that doesn't hang around doing nothing while it's showing.

That program can easily write a NEW program (the launcher) to run the popup and clean up (remove the new program) when it's done.

Sounds difficult?

You might be surprised.

file: test.exec (set permissions += executable)
Code:
#!/bin/sh
# Click on this file to run it

# Copy the text that follows to the launcher file
cat << _EOF > ~/.launcher
#!/bin/sh
# this is a temporary launcher for a test. -rainbow sally
nohup "\$@" >/dev/null 2>&1 &
_EOF

# change perms to make it executable
chmod +x ~/.launcher

# run it to show a popup for three seconds and start a timeout for
# 4 seconds.  The four second timeout is to simulate actually doing 
# some task that takes a bit longer than the popup shows.

msg="
  Hang on, we're pretending to do 
  some work around here... 
  "

~/.launcher kdialog -passivepopup "$msg" 3

# now while that's running we timeout for 4 seconds.
sleep 4

# and report the good news... done at long-last.
kdialog -msgbox "
  All done!
  That will be 55 dollars and 35 cents, please.
  "

# this cleanup can be done at any time after the launcher has been called.
rm ~/.launcher
A more practical application might be to use the popup to display something like "<AppName>: Working... Please wait" while the main thread is aleady busy doing whatever it needs to do behind the scenes.

ALSO: Consider xmessage for the popup messages too. Xmessage is common to all X systems, which include everything from WindowMaker to LXDE, fluxbox, xfce, iceWM, Gnome, to who knows what all.

.
Posted in Uncategorized
Views 34065 Comments 7
« Prev     Main     Next »
Total Comments 7

Comments

  1. Old Comment
    I don't follow what the advantage in making a separate launcher file is?
    Posted 02-04-2012 at 07:37 PM by ntubski ntubski is offline
  2. Old Comment
    The advantages are many.

    One is that you get a new window with a new app without the TONS of commandline noise you get with, say KDE.

    I expect you are not a KDE user.

    Also, this is a way to run apps with 'init' as the parent, independent of the existence of the terminal. Close the terminal and the window remains open. Useful for stuff like opening docs that you can only access by way of the commandline. Open a terminal, launch the docs (/kdevelop man: find', for example) and get rid of the terminal.

    In this example, we just see that it can be done by creating a program with another program.

    Exploring the potentials of linux, you might say.

    This can NOT be done in Windows.

    Thanks for the question.
    Posted 03-31-2012 at 03:58 AM by rainbowsally rainbowsally is offline
  3. Old Comment
    All these advantages you list come from using 'nohup', right? That is, putting
    Code:
    nohup kdialog -passivepopup "$msg" 3 >/dev/null 2>&1 &
    instead of
    Code:
    ~/.launcher kdialog -passivepopup "$msg" 3
    would give the same results.

    If I understand correctly, creating the new program is just a way of factoring out the repeated nohup command. In that case I would suggest using a function instead:
    Code:
    launcher()
    {
        nohup "$@" >/dev/null 2>&1 &
    }
    ...
    launcher kdialog -passivepopup "$msg" 3
    This has the advantage that if you run multiple launchers, they won't step on each other.

    PS you're right, I'm not a KDE user.
    Posted 04-02-2012 at 09:03 PM by ntubski ntubski is offline
  4. Old Comment
    nohup is close. The 50K nohup program does essentially the same thing (as does kshell).

    Try it and see. (Remember the app is clickable.)
    Posted 04-05-2012 at 05:24 PM by rainbowsally rainbowsally is offline
    Updated 04-05-2012 at 05:28 PM by rainbowsally
  5. Old Comment
    ntubski,

    I apologize here. I forgot I was using the nohup version of this app.

    I did try what you suggested, by the way, but for some reason in kde it wouldn't work. Possibly lost something in the environment? Dunno.

    I personally use a C based version of launch, which can be expanded to behave like a Windows 'start' app if one wanted to do that (probably using xdg-mime query filetype or something).

    I use Windows for my dialup modem driver so I'll post the C version in the next comment.

    Whether these things are 'necessary' or not, they may be interesting. If you discover a way to optimize the process, great. But running nohup from the script when clicked on doesn't work.

    Here comes the C version... (Give me a few minutes... to go into my linux partition and get it.)

    :-)
    Posted 04-05-2012 at 05:46 PM by rainbowsally rainbowsally is offline
  6. Old Comment
    [My apologies to LQ if this is a duplicate post. I will create an index soon so I can consolodate these things (links instead of dups) soon.]

    To ntubski and others, a recurring theme in my posts is the ability/need for clickable scripts in linux, especially KDE and Gnome, since these are gui-based desktop managers where everything else is clickable. Therefor clickable scripts SHOULD be allowed if not expected to be written.

    Why do these linux desktops think that suddenly linux tools don't have to work "together"?

    Furthermore, the reason we can't write scripts as clickable gui apps with the default setups is unfathomable, unless they are trying to close open source themselves, which appears to be the case with the rpm/deb packages which, at least in the case of openSUSE, has binary and source packages that do NOT match!

    What gives? Doesn't that violate the open source licensing?

    Let be abuse your patience for one moment longer.

    Where's the greater threat to our 'security'? Hackers? Or unscrupulous developers.

    Think about it. And openSUSE brags about having 30,000 developers worldwide. Do we assume all of them have been vetted? They haven't even vetted their SOURCES!

    How many alleged "hackers" problems that we see in the news are in reality "inside jobs"?. Any admin already has all the access a hacker has to find by way of a security 'leak' somewhere.

    In other words, this is a BIG deal! The greater risk to the security our systems is locking us legit users OUT of our own systems.

    That said... care for a bit of mad computer science? ;-)

    This app will compile with a makefile generated by the old 'makefile-creator c launch' commandline or with 'mc2 -t c launch'. [mc2 has not been uploaded yet as of the time of this writing, Apr 5, 2012.]

    The snippets of interest here are
    • 1. the main app itself and how it closes stdout and stderr to silence the output
    • 2. usage of the fork call in case you (meaning anyone) have never tried it.
    • 3. the unparse_args() call which may be a duplicate at this blog, but can be very useful when using system() calls (which expect args in a singl string) from an argument list (of broken up strings) input to a C/C++ main() function.
      4. maintaining usage strings by way of an easily edited text file. (txt2cstr not included -- there are several utilities that can do this already).

    file: src/launch.c
    Code:
    // does execl() in a fork returning control to a terminal immediately.
    
    #include <stdio.h> // stderr
    #include <stdlib.h> // EXIT_FAILURE
    #include <unistd.h> // fork(), execl(), _exit()
    #include <sys/types.h> // pid_t
    #include "usage.str"
    #include <string.h>
    #include <malloc.h>
    #include <errno.h>
    #include "version.h"
    
    struct
    {
      char warn;
    }flg;
    
    int usage(int err);
    int unparse_args(char* cmdline, int limit, int argc, char** argv);
    int print_version(int err);
    // if error, returns argc of option to list in error display
    int parse_options(int argc, char** argv);
    
    
    int main(int argc, char** argv)
    {
      int err = 0;
      
      // help and version switches
      if(argc == 1)
        return usage(0);
      
      if(argc == 2)
      {
        if(strcmp(argv[1], "--help") == 0)
          return usage(0);
        if(strcmp(argv[1], "-v") == 0)
          return print_version(0);
      }
      
      err = parse_options(argc, argv);
      if(err) 
      {
        printf("Unknown launch option '%s'\n", argv[err]);
      }
      
      // reassemble commandline
      char args[1024];
      err = unparse_args(args, 1000, argc, argv);
      if(err)
      {
        if(err == ENOMEM)
          fprintf(stderr, "Out of memory or args list too long\n");
        else if (err == E2BIG)
          fprintf(stderr, "Argument list too long\n");
        return 1;
      }
      
      // if -q suppress all feedback from the app
      if(!flg.warn)
      {
        fclose(stdout);
        fclose(stderr);
        strcat(args, " > /dev/null 2>&1");
      }
      
      // do the fork and execute the commandline
      pid_t pid;
      
      pid = fork ();
      if (pid == 0)
      {
        
        
        // command path, command name, arg1, arg2, ...
        execl ("/bin/sh", "sh", "-c", args, NULL);
        
        // _exit(code) for threads and forks.
        _exit (EXIT_FAILURE);
      }
      else if (pid < 0)
        return -1;
      else
        return 0;
    }
    
    
    int usage(int err)
    {
      if(err)
        fprintf(stderr, "%s", usage_str);
      else
        fprintf(stdout, "%s", usage_str);
      return err;
    }
    
    // returns 0 on success, or one of the following
    // #define ENOMEM    12  /* Out of memory */
    // #define E2BIG    7  /* Argument list too long */
    // defined in /usr/include/asm-generic/errno-base.h
    
    int unparse_args(char* cmdline, int limit, int argc, char** argv)
    {
      cmdline[0] = 0;
      if(argc < 2)
        return 0;
      
      char* buf = (char*) malloc(limit+256);
      if(!buf)
        return errno = ENOMEM;
      
      char* p = buf;
      char* stopat = buf+limit -1;
      int len;
      
      // point to args list
      argc --;
      argv ++;
      
      // skip option switches
      if(flg.warn)
      {
        argc --;
        argv ++;
      }
      
      //////////////////////////////////////
      // recreate command and commandline
      //////////////////////////////////////
      
      for(int i = 0; i < argc; i++)
      {
        p += sprintf(p, "%s ", argv[i]);
        if(p >= stopat)
        {
          free(buf);
          return errno = E2BIG;
        }
      }
      p[-1] = 0; // terminate
      memcpy(cmdline, buf, p-buf);
      free(buf);
      return 0;
    }
    
    int print_version(int err)
    {
      printf("Launch version %s\n", VERSION);
      return err;
    }
    
    int parse_options(int argc, char** argv)
    {
      // if no options, return
      if(argv[1][0] != '-')
        return 0;
      
      // init opts
        flg.warn = 0; // default
        
        // check args (only 1 so far)
        int arg = 1;
        
        // assume error each iteration
        int err = 1;
        
        // check and break if match
        if(strcmp(argv[arg], "-a") == 0)    
        { 
          flg.warn = 1;
          err = 0;
        }
        
        // return argN where error occurred or 0
        if(err) 
          return arg;
        else 
          return 0;
    }
    file: src/update-strings.exec (executable)
    [Note: in kde this is clickable, just click on it and it's done.]
    Code:
    #!/bin/sh
    cd `dirname "$0"`
    printf "" >.msg
    txt2str usage.txt usage_str usage.str 2>>.msg
    echo 'done.' >> .msg
    kdialog --msgbox "$(<.msg)"
    rm -f .msg
    file: src/usage.str
    Code:
    /* usage.txt converted with txt2cstr */
    const char* usage_str =
    "\n"
    "Usage launch [opt] <command> [parameters]\n"
    "\n"
    "Launch just returns control to the terminal immediately after \n"
    "starting an app.  Useful for launching one or more other \n"
    "applications while retaining ability to use the terminal.\n"
    "\n"
    "Options for launch must preceed the command and it's arguments.\n"
    "\n"
    "Recognized optiions are:\n"
    "  --help          this display\n"
    "  -v              version\n"
    "  -a              allow feedback from the launched app.\n"
    "\n"
    ;
    file: src/usage.txt
    Code:
    Usage launch [opt] <command> [parameters]
    
    Launch just returns control to the terminal immediately after 
    starting an app.  Useful for launching one or more other 
    applications while retaining ability to use the terminal.
    
    Options for launch must preceed the command and it's arguments.
    
    Recognized optiions are:
    --help          this display
    -v              version
    -a              allow feedback from the launched app.
    file: src/version.h
    Code:
    // initial version
    // #define VERSION "1.0"
    
    // changed switch to -w to allow warnings to display.  Defaults to
    // supress warnings and other feedback from launched applications.
    // #define VERSION "1.1"
    
    // Added these notes in the version header.
    // #define VERSION "1.2"
    
    // Can't just close stdout or kde will spin out.  Trying redirect
    // to /dev/null for kde and also closing stdin and stderr to -1
    // for normal user.
    // #define VERSION "1.3"
    
    // Changing switch to -a for 'allow' error and warnings instead
    // of -w, which might be useful for a windows style /w(ait) 
    // if anyone uses this for a 'start' clone.
    // #define VERSION "1.4"
    
    // Release .1 corrects a misspelling in this file.
    #define VERSION "1.4.1"
    Thanks for the blog, LQ.

    :-)
    Posted 04-05-2012 at 06:49 PM by rainbowsally rainbowsally is offline
  7. Old Comment
    Quote:
    Originally Posted by rainbowsally
    Try it and see. (Remember the app is clickable.)
    Eh, I don't really want to install all of KDE to test this out

    Quote:
    But running nohup from the script when clicked on doesn't work.
    That's weird, I guess KDE has some strange requirements on clicked shell scripts? It works from the command line right? I also see you have
    Code:
    // Can't just close stdout or kde will spin out.
    which appears to be another strange requirement. I feel like these ought to be documented somewhere, but I can't find them

    Quote:
    the unparse_args() call which may be a duplicate at this blog, but can be very useful when using system() calls (which expect args in a singl string) from an argument list (of broken up strings) input to a C/C++ main() function.
    That function will give you surprising results if there are special characters in the arguments. Also I think by using snprintf() you can avoid the malloc().
    Posted 04-05-2012 at 07:40 PM by ntubski ntubski is offline
 

  



All times are GMT -5. The time now is 11:44 AM.

Main Menu
Advertisement
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration