2

I wrote a little bash script showing a screenshot menu (screenshot-menu.bash):

#!/usr/bin/env bash
echo 'Select an option:

s whole screen w active window r select region ' read -rsn1 key sleep 0.1

case $key in s|w|r) nohup ./screenshot.bash -$key & ;; *) echo Canceled. >> ./log.txt ;; esac

This script runs nohup ./screenshot.bash with a parameter. This is screenshot.bash:

#!/usr/bin/env bash
while [[ "$#" > 0 ]]; do case $1 in
  -s|--screen) PARAM=''; TYPE='Screen'; shift;;
  -w|--window) PARAM='-u'; TYPE='Window'; shift;;
  -r|--region) PARAM='-s'; TYPE='Region'; shift;;
  *) echo "Unknown parameter passed: $1"; exit 1; shift; shift;;
esac; done

sleep 0.2 scrot $PARAM $(xdg-user-dir PICTURES)/'%Y-%m-%d %H.%M.%S $wx$h '$TYPE.png -e 'echo "$f" && notify-send -u low "$f"'

When I run ./screenshot-menu.bash inside a terminal, then enter r, everything works fine: I can select a region and I get a screenshot of that region.

But I want to be able to call this script from everywhere. That's why I have a keybinding that runs this script in a new terminal window:

alacritty -t "Screenshot menu" -o "window.dimensions.columns=20" -o "window.dimensions.lines=7" -e ~/bin/screenshot-menu.bash

(Or simpler gnome-terminal -- ~/bin/screenshot.bash.)

That's the reason I use nohup. I want the terminal to exit because I don't want to have it in the screenshot. But of course, screenshot.bash must not die.

The second method (script in a new terminal window) works maybe once in five times. The other times, the cursor doesn't turn into a cross-hair for selecting a region. I suppose, screenshot.bash dies although I use nohup.

What could be causing this random behavior?

MaxGyver
  • 309
  • 1
    Based on a bit of testing, I'd guess the shell that runs screenshot-menu.bash gets a SIGHUP and exits before nohup ./screenshot.bash can actually run. You may be able to confirm this by adding a short sleep at the end of screenshot-menu.bash. I'm not saying this would be a good solution, though; there likely is a better approach. – fra-san Apr 02 '21 at 15:17
  • ...sorry, I meant "the process forked off screenshot-menu.bash" in place of "the shell that runs screenshot-menu.bash". – fra-san Apr 02 '21 at 15:26
  • 1
    I'd way you're running into a race condition. Why do you have the sleep calls? – Eduardo Trápani Apr 02 '21 at 15:55
  • @fra-san: sleep 0.1 at the end seems to fix it! I tried it five times and it worked every time. Thank a lot! – MaxGyver Apr 02 '21 at 18:36
  • @EduardoTrápani: There is a sleep because otherwise the keyup event of the selection (for example key r) cancels scrot immediately. I added a second sleep when I split this script into two because I wanted to make sure, the terminal closes before the screenshot is taken. But one sleep only in screenshot.bash should be sufficient. – MaxGyver Apr 02 '21 at 18:38

1 Answers1

1

The issue is likely that the process forked by screenshot-menu.bash to execute nohup ./screenshot.bash -$key in the background receives a SIGHUP and exits before nohup can run.

A simple experiment seems to confirm this:

$ alacritty -e sh -c 'nohup sleep 1000 &'
$ pgrep -a sleep
$

straceing the whole command shows that sh forks, but there is no execve running nohup before the forked process receives a SIGHUP — when alacritty terminates and the pseudo-terminal it created disappears — and exits.

Without looking for alternative approaches (which likely exist when it comes to mixing terminal and graphical applications), a logical solution is to avoid applying nohup to the command that is going to the background and use it on the parent process instead. For instance:

$ alacritty -e sh -c 'nohup sh -c "sleep 1000 &"'
$ pgrep -a sleep
625910 sleep 1000

In screenshot-menu.bash you may thus change this line

s|w|r) nohup ./screenshot.bash -$key & ;;

into

s|w|r) nohup sh -c "./screenshot.bash \"-$key\" &" ;;

Alternatively, you may enable job control in screenshot-menu.bash with set -m and get rid of nohup (the shell will then start background pipelines (a simple command is a pipeline, too) in their own process groups, and only the foreground process group is delivered a SIGHUP when its controlling terminal disappears), provided you are fine with ensuring that nothing in screenshot.bash tries to read from or write to the terminal (i.e. you need explicit redirections equivalent to those performed by nohup).

For instance, screenshot-menu.bash may become:

#!/usr/bin/env bash

set -m

echo 'Select an option: ... '

case $key in s|w|r) </dev/null >>./log.txt 2>&1 ./screenshot.bash "-$key" & ;; *) echo Canceled. >>./log.txt ;; esac

See also:

fra-san
  • 10,205
  • 2
  • 22
  • 43