0

I've read this : Parentheses for subshell don't work when stored in a variable and this : http://mywiki.wooledge.org/BashFAQ/048 and they've been insightfull and instructive and I've enjoyed reading them.

I did not find therin a solution to a reccurent problem of wanting refactored code in shell that eval, up until I wanted to throw subshells into the mix, could (dirtily) answer.

Thus, I am forced to agree that eval is evil and has technical shortcomings on top of the security shortcomings.

How do you solve the following :

  1. According to a switch-case splitting you accross different linux distributions you have different second threads to your script opened in another terminal, with matching close syntax.

  2. The body is the same but does use variables filled within the first thread. And is NOT a single command

  3. The body itself runs subshells

  4. The replacing solution need to not be sequential but "synchronous" as eval is. (it does not make trailing commands wait for it's return to execute)

Here's what we get :

Our if and elif switcher which starts our scripts second terminal or "second thread" :

if [[ "$operating_system" = "Ubuntu" || "$operating_system" = "Debian GNU/Linux" ]]
then
    eval "$gnome_opening_faf_script $faf_script $gnome_closing_faf_script"
elif [ "$operating_system" = "Kubuntu" ]
then
    eval "$konsole_opening_faf_script $faf_script $konsole_closing_faf_script"
elif [ "$operating_system" = "elementary OS" ]
then
    eval "$io_opening_faf_script $middlescript $io_closing_faf_script"
else
    eval "$xterm_opening_faf_script $faf_script $xterm_closing_faf_script"
fi

The second thread's main body variable :

faf_script='echo "expecting you to type in Forged Alliances Launch options";
echo "reminder : look in your home folder, theres a file there with the contents to be pasted";
echo "once thats done edit steam settings in order to enable Proton for all games";
steam -login '$steam_user_name' '$steam_password' -applaunch 9420 &
echo "waiting for Forged Alliance to be installed, Game.prefs to exits and Forged Alliance to be shut down";
echo "you may also type \"continue\" to exit this while loop"
echo -n "if you feel the conditions for continuing sucessfully have been met... ";
( i=1;
sp="/-\|";
no_config=true;
while $no_config;
do printf "\b${sp:i++%${#sp}:1}";
[[ ! $(pidof SupremeCommande) && -f $origin/steamapps/compatdata/9420/pfx/drive_c/users/steamuser/Local\ Settings/Application\ Data/Gas\ Powered\ Games/Supreme\ Commander\ Forged\ Alliance/Game.prefs ]] && no_config=false;
sleep 1;
done;
kill $$;
) &;
child_pid=$!;
while $no_config;
do read -r typed_continue;
[[ "$typed_continue" = "continue" ]] && no_config=false;
sleep 1;
done;
kill $child_pid;
echo "";
'

And the opening an closing variables that allow for the second thread of the script to be run by different terminals depending on the distribution.

gnome_opening_faf_script='gnome-terminal --tab --active --title="install & run steam, steamcmd, FA" -- bash -c '"'"''
konsole_opening_faf_script='konsole -e /bin/bash --rcfile <(echo '"'"''
io_opening_faf_script='io.elementary.terminal -e "bash -c '"'"'curl  wttr.in/bydgoszcz'"'"';'"'"'sleep 3'"'"''
xterm_opening_faf_script='xterm -T "install & run steam, steamcmd, FA" -e '"'"''

gnome_closing_faf_script='gnome-terminal -- bash -c "cd faf; ./downlords-faf-client";'"'"''
konsole_closing_faf_script='konsole -e /bin/bash --rcfile <(echo "cd faf; ./downlords-faf-client; exit 0") &'"'"') &'
io_closing_faf_script='io.elementary.terminal -e "cd faf; ./downlords-faf-client";'"'"''
xterm_closing_faf_script='xterm -T "FAF" -e "cd faf; ./downlords-faf-client";'"'"' &'

Usually when people suggest replacements to eval the contexts are beyond simplified. eval is running a single echo "hello world".

here it is not my case and I've been able to apply none of the solutions.

tatsu
  • 2,316
  • 7
  • 43
  • 87
  • 2
    Oh my. You need to define some functions instead of cramming scripts into strings. – chepner Apr 26 '19 at 13:27
  • well how would I run those functions as part of a terminal tab then? this is more compex then it looks, read ahead. – tatsu Apr 26 '19 at 13:29
  • I'm still trying to decipher what you have to figure that out. – chepner Apr 26 '19 at 13:32
  • What do you mean by "run as part of a terminal tab"? What prevents you from making function calls instead of using `eval`? – Benjamin W. Apr 26 '19 at 13:35
  • the context it the following : the script must run on most linux distributions whislt installing the least possible extra software. The second need is to run parts of the script in different windows so as to gain time. As such a new terminal creation needs to both be able to call the appropriate type of preinstalled termianl with appropriate syntax as well as refactoring the code in between the opening and closing syntaxes. – tatsu Apr 26 '19 at 13:37
  • 1
    "run parts of the script in different windows so as to gain time" -- why don't you run those parts in the background. It seems wrong to rely on a GUI app to run your shell script. – glenn jackman Apr 26 '19 at 13:38
  • I'm inclined to agree here. Functions are likely a *much* better solution than `eval`. – Paul Hodges Apr 26 '19 at 13:40
  • these commands ouput progress precentage and stats. I need this command-line output to be visible to the user as these commands take a good while and the user needs to be reassured that progress is being made. I actually am more than fine with switching to functions so long as they can actually fullfill the above. – tatsu Apr 26 '19 at 13:41
  • Why are you opening two separate terminal windows? Can you just run `downloads-faf-client` as soon as `faf_script` completes in the *same* window? – chepner Apr 26 '19 at 13:51
  • esthetics. faf outputs alot of dialog. I guess it's not needed but this doesn't really solve the main issue, just makes it a tad smaller. the whole second part of the script still has to run in parralell with the first part of the script not shown here. – tatsu Apr 26 '19 at 13:54
  • I'm forced to agree though, functions WOULD be better. I have just been scrtaching my head at how I would make them accomplish the same thing to my knowledge you can't do `gnome-terminal --tab --active --title="install & run steam, steamcmd, FA" -- bash -c 'function $steam_user_name $steam_password 'gnome-terminal -- bash -c "cd faf; ./downlords-faf-client";'` can you? – tatsu Apr 26 '19 at 14:11

2 Answers2

2

Let's start with a high-level overview of what you want to do: run some number of arbitrary scripts (contained in strings) in a separate tab of an OS-specific terminal emulator. The contents of the scripts doesn't really matter, so let's just say we have them in two variables:

faf_script='...'
download_script='...'

Now, we'd like a function that looks something like this:

run_scripts () {
    for script in "$@"; do
        run_in_tab "$script"
    done
}

where run_in_tab is an OS-specific function that runs its argument in a new tab of the desired terminal emulator. Maybe it doesn't actually open a new tab; maybe it opens an entirely new window, but run_scripts doesn't care about that. It just needs a function that will run a shell script in a way that the user can interact with it.

Next, we actually define run_in_tab in an OS-specific way.

case $operating_system in
  Ubuntu|"Debian GNU/Linux")
    run_in_tab () {
      gnome-terminal .....
    } ;;

  Kubuntu)
    run_in_tab () {
        konsole ......
    } ;;

  "elementary OS")
    download_script='...'   # For whatever reason, this is different; override it
    run_in_tab () {
        konsole ......
    } ;;

  *) run_in_tab () {
       xterm ....
     } ;;
esac

Once that's done, we simply call run_scripts:

run_scripts "$faf_script" "download_script"
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Okay I'll give that an implementation run. it takes awhile to apply to the whole of my script so I'll get back to you. have an upvote! – tatsu Apr 26 '19 at 14:16
  • That's whatever command you use to run the value of `download_script` or `faf_script` in a new tab using that particular terminal emulator. – chepner May 05 '19 at 14:08
0

An alternative which basically does the same thing as eval, but is easier (at least for me) in complex cases like this:

goDo() {
  local tmp=$(mktemp) # create a guaranteed unique temporary filename
  echo "$1" >| $tmp   # send the passed-in string to that file
  . $tmp              # *source* the file back into the local context
}

"sourcing" a file means reading it as if it's actual content had been typed instead of the line that sources it - almost literally. The context of the calling script are maintained. traps, variables, functions, aliases - everything available in the calling script is available to the code being called. This is virtually the only way a called script can set a variable in the caller, because this doesn't run the code in a subshell.

If anything breaks you have the whole file available to debug. I recommend setting set -x debugging on for the first few test runs just to make sure you see what you expect.

Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
  • just type set -x into the terminal or script? – tatsu Apr 26 '19 at 13:25
  • Pretty much. You can put `set -vx` on a line above the part you are debugging, and `set +vx` below if you want to shut up the extra output. The `v` will send to stderr the command it's about to execute from your script, *before* parsing. The `x` will do the same *after* it's been through the parser, preceeded by `$PS4`, so that you can see the expansions of all the aliases, globs, and variables, etc. – Paul Hodges Apr 26 '19 at 13:29
  • nice, and the `goDo` replaces eval but is cleaner? – tatsu Apr 26 '19 at 13:32
  • 1
    Hopefully. You understand what it's doing? Let me break it out in more detail. – Paul Hodges Apr 26 '19 at 13:33
  • I'm running another test on another thread I have on another similar sort of problem I have with the same script. since it takes a while to test, It might take a bit for me to test your solution but I've upvoted you in the meantime. – tatsu Apr 26 '19 at 13:44