Simply, I call the script (brightness.sh) and pass a three character value as an argument. I've got commands to strip individual numbers for math processes, and then the final commands to apply the changes. The rest is just simple if-then stuff.

I know I've got a lot of commands calling xrandr data, and that's slowing down the whole script. I would love to reduce it to just one instance of data collection, but I'm not skilled enough to make a super complex piped command that could do all that in one line.

What should I do to make this run more efficiently? Like, would it be elegant to put all of the --verbose output into a variable, or file, then grep that variable/file for even faster processing? I would think all that data wouldn't fit into a variable.

#!/bin/bash
: '
 gonna need some variables for later
 collect some data
'

gv1=$(xrandr --verbose | grep -m 1 Gamma | awk '{print $2}')
gv2=$(xrandr --verbose | grep -m 2 Gamma | awk '{print $2}' | tail -n1)
c1=$(xrandr --verbose | grep -m 1 Brightness | awk '{print $2}')
c2=$(xrandr --verbose | grep -m 2 Brightness | awk '{print $2}' | tail -n1)

r1=$( echo $gv1 | cut -d':' -f1 )
g1=$( echo $gv1 | cut -d':' -f2 )
b1=$( echo $gv1 | cut -d':' -f3 )
r2=$( echo $gv2 | cut -d':' -f1 )
g2=$( echo $gv2 | cut -d':' -f2 )
b2=$( echo $gv2 | cut -d':' -f3 )
c=1.0
gm=0.01
br=0.1

# default values so the stupid thing doesn't freak out
cv1=$c1
cv2=$c2

: '
 RGB is red green blue, C is Brightness, gm is delta gamma, br is delta brightness
 only one value is coming in from the command line, which indicates what screen is changing with which value.
 code being passed: i for inc, d for dec, b for balance (set to 1.0)
                  : r for red, g for green, b for blue
                  : 1 for DVI-I-1, 2 for DVI-I-2
If you use this script, run 'xrandr --verbose' in a terminal to see which screens are connected, then set the appropriate data here.
'

case $1 in
    # #######
    # brightness
    # #######
  dc2)
    cv2=$( echo "$c2 - $br" | bc -l )
    ;;
  ic2)  
    cv2=$( echo "$c2 + $br" | bc -l )
    ;;
  bc2)
    cv2=$c
    ;;
  dc1)
    cv1=$( echo "$c1 - $br" | bc -l )
    ;;
  bc1)
    cv1=$c
    ;;
  ic1)
    cv1=$( echo "$c1 + $br" | bc -l )
    ;;

    # #######
    # colors
    # #######
  ir1)
    rv=$( echo  "$r1 + $gm" | bc -l )
    gv1=$rv:$g1:$b1
    ;;
  br1)
    gv1=$c:$g1:$b1
    ;;
  dr1)
    rv=$( echo  "$r1 - $gm" | bc -l )
    gv1=$rv:$g1:$b1
    ;;
  ir2)
    rv=$( echo  "$r2 + $gm" | bc -l )
    gv2=$rv:$g2:$b2
    ;;
  br2)
    gv2=$c:$g2:$b2
    ;;
  dr2)
    rv=$( echo  "$r2 - $gm" | bc -l )
    gv2=$rv:$g2:$b2
    ;;
  ig1)
    rv=$( echo  "$g1 + $gm" | bc -l )
    gv1=$r1:$rv:$b1
    ;;
  bg1)
    gv1=$r1:$c:$b1
    ;;
  dg1)
    rv=$( echo  "$g1 - $gm" | bc -l )
    gv1=$r1:$rv:$b1
    ;;
  ig2)
    rv=$( echo  "$g2 + $gm" | bc -l )
    gv2=$r2:$rv:$b2
    ;;
  bg2)
    gv2=$r2:$c:$b2
    ;;
  dg2)
    rv=$( echo  "$g2 - $gm" | bc -l )
    gv2=$r2:$rv:$b2
    ;;
  ib1)
    rv=$( echo  "$b1 + $gm" | bc -l )
    gv1=$r1:$g1:$rv
    ;;
  bb1)
    gv1=$r1:$g1:$c
    ;;
  db1)
    rv=$( echo  "$b1 - $gm" | bc -l )
    gv1=$r1:$g1:$rv
    ;;
  ib2)
    rv=$( echo  "$b2 + $gm" | bc -l )
    gv2=$r2:$g2:$rv
    ;;
  bb2)
    gv2=$r2:$g2:$c
    ;;
  db2)
    rv=$( echo  "$b2 - $gm" | bc -l )
    gv2=$r2:$g2:$rv
    ;;
  *)
    ;;
esac

    # #######
    # apply changes
    # #######
    xrandr --output DVI-I-1 --brightness $cv1 --gamma $gv1
    xrandr --output DVI-I-2 --brightness $cv2 --gamma $gv2

Update

So far the worst (maybe) way I've found to increase the speed of this script is to export six variables, allowing them to be altered in script:

gv1 gv2 cv1 cv2 c1 c2

With these on the outside, they can exist at least for the session you're running. Once exported, just comment out the xrandr calls and the script runs much faster.

As an aside, I'm having issues with xrandr not reporting the same gamma values I enter. I've partially solved this with the exported variables.

Update

In case you're unsure how to export the variables, use the following commands in another script, or enter at the cli.

gv1=$(xrandr --verbose | grep -m 1 Gamma | awk '{print $2}')
gv2=$(xrandr --verbose | grep -m 2 Gamma | awk '{print $2}' | tail -n1)

c1=$(xrandr --verbose | grep -m 1 Brightness | awk '{print $2}')
c2=$(xrandr --verbose | grep -m 2 Brightness | awk '{print $2}' | tail -n1)

cv1=$c1
cv2=$c2

export gv1 gv2 cv1 cv2 c1 c2

In the first script (once these are exported) you'll need to invert the cvx assignment to allow for new values being set environmentally and not retrieved programmatically... That is, the variables are now outside the script, so refreshing them from the script will override the data retrieved from the environment. In any case:

c1=$cv1
c2=$cv2

will be the new variable exchange in the script.

P.S. you'll have to export the variables every time you start a new session, unless you add them to the .bashrc, or run a script to retrieve and re-export the variables on log-in.

  • @200_success: That's an awesome reply. Thank you for taking time out to look at my basic approach and giving appropriate guidance. I did not know awk was so versatile. I actually knew nothing of awk beyond what examples I picked up showed how it was used. – Niko Apr 20 '16 at 23:07
up vote 4 down vote accepted

Your fundamental strategic error is trying to accomplish this task mostly in Bash. It's not impossible, but, as you have noticed, it's not a good tool for the job. Adversities include:

  • Parsing input from xrandr --verbose. You have ended up running xrandr --verbose many times, extracting one bit of information each time, which is obviously suboptimal.

    As you noted, one simple way to avoid executing it many times is to save the output as a variable: XRANDR_OUTPUT=$(xrandr --verbose).

    Another way is to pipe the output of xrandr --verbose into a Bash while read loop.

  • Slicing and dicing text. Parsing the gamma color components using multiple cut commands is awkward.
  • Arithmetic. Though Bash has support for integer arithmetic, floating-point arithmetic requires an external command.

What to do instead? There are a number of programming languages that you could pick from. However, since you are already using awk, why not take full advantage of it? AWK is much more suited to nearly everything in this task.

Hard-coding

You've hard-coded support for two screens. A more important concern, though, is that you would have to duplicate huge chunks of code to add support for more screens.

Suggested solution

This solution consists nearly entirely of an AWK script; Bash merely pipes xrandr --verbose to it. You could probably reformulate it entirely in AWK by using getline to do the pipe.

If you just want to do a dry run, change system(cmd) at the end to print cmd.

#!/bin/bash

xrandr --verbose | awk -v vars="$*" '
    BEGIN {
        # Constants
        RESET = 1.0;
        BRIGHTNESS_DELTA = 0.1;
        GAMMA_DELTA = 0.01;

        # Declare arrays
        delete brightness; delete brightness_new;
        delete gamma; delete gamma_new;
        delete screens_to_adjust;
    }

    /Brightness/ { brightness[1 + length(brightness)] = $2; }
    /Gamma/      { gamma[1 + length(gamma)] = $2; }

    END {
        split(vars, args);
        for (arg in args) {
            split(args[arg], verb_noun_screen, "");
            verb = verb_noun_screen[1];
            noun = verb_noun_screen[2];
            screen = verb_noun_screen[3];
            if (!index("dib", verb)) continue;

            if (noun == "c") {                          # Brightness
                brightness_new[screen] = \
                    (verb == "d") ? (brightness[screen] -= BRIGHTNESS_DELTA) :
                    (verb == "i") ? (brightness[screen] += BRIGHTNESS_DELTA) :
                                    (brightness[screen]  = RESET);
                screens_to_adjust[screen] = 1;

            } else if (color = index("rgb", noun)) {    # Gamma component
                split(gamma[screen], rgb, ":");
                rgb[color] = \
                    (verb == "d") ? (rgb[color] -= GAMMA_DELTA) :
                    (verb == "i") ? (rgb[color] += GAMMA_DELTA) :
                                    (rgb[color]  = RESET);
                gamma_new[screen] = gamma[screen] = sprintf("%g:%g:%g", rgb[1], rgb[2], rgb[3]);
                screens_to_adjust[screen] = 1;
            }
        }

        for (screen in screens_to_adjust) {
            cmd = sprintf( \
                "xrandr --output DVI-I-%d%s%s", \
                screen, \
                (screen in brightness_new ? \
                    sprintf(" --brightness %f", brightness_new[screen]) : ""), \
                (screen in gamma_new ? \
                    sprintf(" --gamma %s", gamma_new[screen]) : "") \
            );
            system(cmd);
        }
    }
'

Your other idea

You had the idea to make a script that modifies environment variables in the invoking shell. I don't think that that works: child processes don't change environment variables in the parent. If you want to do that, though, there are two ways:

  • eval $(some_script), where some_script is a program that prints out var1=value1 assignments (taking care to escape special characters properly!).
  • Write a shell function instead of a program. Then that function executes in the same shell, not a child.

Anyway, all of that is moot, since the suggested solution using AWK is more suitable anyway.

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.