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
to
[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.
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)
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.
.
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
Code:
nohup kwrite /etc/fstab >/dev/null 2>&1 &
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?That should suffice for the common sense and the warning. :-)
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.
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
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.
.
Total Comments 7
Comments
-
I don't follow what the advantage in making a separate launcher file is?
Posted 02-04-2012 at 07:37 PM by ntubski -
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 -
All these advantages you list come from using 'nohup', right? That is, putting
Code:nohup kdialog -passivepopup "$msg" 3 >/dev/null 2>&1 &
Code:~/.launcher kdialog -passivepopup "$msg" 3
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
PS you're right, I'm not a KDE user.Posted 04-02-2012 at 09:03 PM by ntubski -
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
Updated 04-05-2012 at 05:28 PM by rainbowsally -
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 -
[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; }
[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
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" ;
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.
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"
:-)Posted 04-05-2012 at 06:49 PM by rainbowsally -
Quote:Originally Posted by rainbowsallyTry it and see. (Remember the app is clickable.)
Quote:But running nohup from the script when clicked on doesn't work.
Code:// Can't just close stdout or kde will spin out.
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.Posted 04-05-2012 at 07:40 PM by ntubski