1

What I am trying to do is (in Mac OS X / MacOS) create a dialog in using osascript. I'd like to add an icon to it. This part is easy and works.

However, the problem is that if the user is in dark or light mode my icon file won't look right. So, I've got two .icns files which is stored on all Macs in my environment in a specific directory (that directory is located at /Library/ctxsit/). I can call up one or the other using a variable, but when I add a function to determine which file to use, it fails.

First, the part that works is:

icon=(/Library/ctxsit/CompanyIconB.icns)
title="Company IT Notification"

echo $title

prompt=$(osascript -e "display dialog \"Click Continue to logout from current user and set this Mac back to the Welcome Screen. This will leave the current user directory and profile intact.

Please make sure your work is saved. After Continuing, the device will appear to be in a Fresh OS state.  
        \" buttons {\"Cancel\", \"Continue\"} with title \"$title\" default button 2 with icon \"$icon\" ")
if [[ $prompt == "button returned:Continue" ]] 
then
    sleep 2
    prompt=$(osascript -e "display dialog \"Are you sure? Clicking Continue will run the script and Restart this Mac. 

        \" buttons {\"Continue\", \"Cancel\"} with title \"$title\" default button 2 with icon \"$icon\" ")

fi

If the Mac is in Dark mode, then that first line of my code would have

icon=(/Library/ctxsit/CompanyIconW.icns)

All above is in the "working" version of my script. So, I have added a function to the "non-working" version.


Next, the part which does not work is:

#!/bin/bash

currentOS=$(sw_vers -productVersion)
currentUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )
#mode=$(sudo -u "$currentUser" defaults read -g AppleInterfaceStyle)
OScheck=$([[ (($currentOS < 10.14)) ]]; echo $?)


function setIcon()
{
    if [[ $OScheck == "1" ]]; then
        echo "Mojave or newer"
        if [[ $mode == "Dark" ]]; then
            echo "Dark mode enabled"
            icon="CompanyIconW.icns"
        else
            echo "Light mode enabled"
            icon="CompanyIconB.icns"
        fi
    else
        echo "Older than Mojave"
        icon="CompanyIconB.icns"
    fi
}
setIcon
echo $icon

title="Company IT Notification"
echo $title

prompt=$(osascript -e "display dialog \"Click Continue to logout from current user and set this Mac back to the Welcome Screen. This will leave the current user directory and profile intact.

Please make sure your work is saved. After Continuing, the device will appear to be in a Fresh OS state.  
        \" buttons {\"Cancel\", \"Continue\"} with title \"$title\" default button 2 with icon \"$icon\" ")


if [[ $prompt == "button returned:Continue" ]] 
then

    sleep 2
    prompt=$(osascript -e "display dialog \"Are you sure? Clicking Continue will run the script and Restart this Mac. 
        \" buttons {\"Continue\", \"Cancel\"} with title \"$title\" default button 2 with icon \"$icon\" ")

fi

Finally, when I run this script from terminal, I get this result:

bash-3.2$ ./Scratch2.sh
Mojave or newer
Light mode enabled
CompanyB.icns
Company IT Notification
0:393: execution error: A resource wasn’t found. (-192)
0:134: execution error: A resource wasn’t found. (-192)

The only part I can think of to address is how in my "working" version, I have an actual path-to-file whereas my "non-working" version does not include that piece. This leads to my main question:

How can I set a variable based off of the return/result/exit code/ of a function?

e.g. take whichever mode the computer is in (light, dark) and use file 1 for light and file 2 for dark.

Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
ca11is0n
  • 11
  • 1

2 Answers2

2

Some bash's tricks

Here is other robust way for doing this...

Consider Why avoid subshells is a good practice, here a no other forks than to apple binaries.

Populating variables:

read -r currentOS < <(exec sw_vers -productVersion)
while IFS=$' :\t\r\n' read -r lhs rhs ;do
    [ "$lhs" = "Name" ] && currentUser=$rhs
done   < <(exec Scutil <<<"show State:/Users/ConsoleUser")

Comparing version number:

compareOSver() {
    local osMaj osMin osSub cStr1 cStr2
    local -i retVal
    IFS=. read -r osMaj osMin osSub _ <<<"$1"
    printf -v cStr1 %06d "$osMaj" "$osMin" "$osSub"
    IFS=. read -r osMaj osMin osSub <<<"$2"
    printf -v cStr2 %06d "$osMaj" "$osMin" "$osSub"
    retVal="10#${cStr1} > 10#${cStr2} ? 1 : 10#${cStr2} > 10#${cStr1} ? 2 : 0"
    return $retVal
}

Passing variable from function to script:

function setIcon() {
    local returnVar=${1:-iconRes}
    compareOSver "$2" "$3"
    case $? in
        1 ) printf -v $returnVar 'Newer_OS.icn' ;;
        2 ) printf -v $returnVar 'Older_OS.icn' ;;
        * ) printf -v $returnVar 'Same_OS.icn' ;;
    esac
}

So you could:

setIcon icon "$currentOS" 10.14

My first tries using osascript:

DialogStr='Click Continue to logout from current user and set this Mac back to \
the Welcome Screen. This will leave the current user directory and profile intact.

Please make sure your work is saved. After Continuing, the device will appear to \
be in a Fresh OS state.'

title="Company IT Notification"

printf -v osaCmd 'display dialog "%s" buttons {"Cancel","Continue"} \
        with title "%s" default button 2 with icon %d' \
        "${DialogStr//$'\\\\\n'}" "$title" 0

IFS=: read -r _ userAnswer < <(exec osascript -e "${osaCmd//$'\\\\\n'}")

Ok I've find here: Change icon of notification when using osascript a way of using alternate icon, correct syntax is icon alias "file:path:in:mac:format:icon.icn"

testIcon=/Volumes/Macintosh\ HD/Applications/Utilities/Terminal.app/Contents/Resources/term_icon.icns 

Then

testIcon=${testIcon#/Volumes/}
printf -v osaCmd 'display dialog "%s" buttons {"Cancel","Continue"} \
    with title "%s" default button 2 with icon alias "%s"' \
    "${DialogStr//$'\\\\\n'}" "$title" "${testIcon//\//:}"

IFS=: read -r _ userAnswer < <(exec osascript -e "${osaCmd//$'\\\\\n'}")
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
1

The way you assign the value icon is "right", but I think your condition expression is wrong:

OScheck=$([[ (($currentOS < 10.14)) ]]; echo $?)

Besides the fact that you use < instead of -lt, decimal arithmetic doesn't work in Bash, so you would have to rely on external tools like bc:

OScheck=$(echo "$currentOS < 10.14" | bc) # 1 = true, 0 = false

Update your case statement accordingly.

As a side note, icon=(/Library/ctxsit/CompanyIconB.icns) is virtually the same as icon="CompanyIconB.icns" except the first one is an explicit assignment to an array, and the second one does not include a directory path.

Also avoid "returning values" from a function by using command substitution as it is terribly inefficient. Use a temporary global variable instead.

# Don't do this!
function x { echo "This is my result."; }
my_var=$(x) 

# Do this instead.
function x { __="This is my result."; }
x
my_var=$__

Update

As mentioned in the comments, comparing version numbers as decimals is not accurate, so I suggest using a function that compares them. This needs format of input arguments checked.

function compare_versions {
    local a=${1%%.*} b=${2%%.*}
    [[ "10#${a:-0}" -gt "10#${b:-0}" ]] && return 1
    [[ "10#${a:-0}" -lt "10#${b:-0}" ]] && return 2
    a=${1:${#a} + 1} b=${2:${#b} + 1}
    [[ -z $a && -z $b ]] || compare_versions "$a" "$b"
}
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • 1
    Comparing version numbers by relational operators (`<` or `>`) is not a correct method. Assume that `currentOS` is `10.2`. As a decimal number `10.2` is greater than `10.14`, but as a version number `10.14` is greater than `10.2` . – M. Nejat Aydin Jan 01 '22 at 08:45
  • Good catch. That one too. So I guess he would have to split it into two parts and compare. – konsolebox Jan 01 '22 at 08:47
  • 1
    @M.NejatAydin Have a look at *my* [`compareOSver` function](https://stackoverflow.com/a/70548469/1765658)! There as some bashisms... – F. Hauri - Give Up GitHub Jan 01 '22 at 12:05
  • @compareOSver Thank you for sharing your function. I realized I need to make the arguments explicitly decimal. – konsolebox Jan 01 '22 at 12:12
  • 1
    Something like that could be simpler to compare versions: `[[ $(IFS=.; printf '%09d' $v1) < $(IFS=.; printf '%09d' $v2) ]]`, with the assumption that versions `$v1` and `$v2` consist of decimal digits and dots. – M. Nejat Aydin Jan 01 '22 at 13:07
  • 1
    @M.NejatAydin In my function, I'll consider major, minor **and** sub-release!, All are merged in one string, using: `printf -v cStr1 %06d "$osMaj" "$osMin" "$osSub"`. (note quotes around `%06d` are useless here;), but double-quottes around `$osSub` ensure a `0` will be used as default. – F. Hauri - Give Up GitHub Jan 01 '22 at 13:48
  • @M.NejatAydin That's quite neat and good enough for immediate comparisons. – konsolebox Jan 01 '22 at 13:50
  • @M.NejatAydin `[ $( printf ...) < $( printf ... ) ]` implie two **forks**, which are perforamce killer! – F. Hauri - Give Up GitHub Jan 01 '22 at 13:52
  • What if `$currentOS` contain `10.14.3` ?? – F. Hauri - Give Up GitHub Jan 01 '22 at 13:54
  • 1
    @F.Hauri M. Nejat Aydin's method can be changed to `printf -v v1x '%09d' ${v1//./ }; printf -v v2x '%09d' ${v2//./ }; [[ $v1x < $v2x ]]` although it gets a little bit messy there. Personally I would still use a function. Still his method is good enough for immediate tests, and it should also work on version numbers with varying number of parts. – konsolebox Jan 01 '22 at 14:25
  • 1
    @konsolebox Again, what if `v1='10.14' v2='10.14.2'` ? ( I'll prefer to use functions too, and still trying to avoid useless forks... ;-) – F. Hauri - Give Up GitHub Jan 01 '22 at 14:36
  • 1
    @F.Hauri It works well with those. `<` and `>` are string comparisons. – konsolebox Jan 01 '22 at 14:54
  • I just noticed that it won't work with `v1='10.14' v2='10.14.0'` as they should be equal. – konsolebox Jan 01 '22 at 15:02
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/240631/discussion-between-f-hauri-and-konsolebox). – F. Hauri - Give Up GitHub Jan 01 '22 at 15:17
  • The `.0` pattern is an issue indeed. Not only at the end but anywhere in the version string, because, after word splitting, a number beginning with `0` is interpreted as an octal number by `printf`'s `%d`. This version should fix that: `[[ $(IFS=.; printf '%09d' ${v1//.+(0)/.}) < $(IFS=.; printf '%09d' ${v2//.+(0)/.}) ]]`. Note that this needs `shopt -s extglob` – M. Nejat Aydin Jan 01 '22 at 18:02
  • Still doesn't work for this case: `v1=1.1 v2=1.1.0.0`. Also it might be a little bit extreme but so won't `v1=08.1 v2=09.1`. – konsolebox Jan 01 '22 at 18:14
  • 1
    @konsolebox Ok, one more trick; `[[ $(IFS=.; printf %09.0f ${v1%%.*([.0])}) < $(IFS=.; printf %09.0f ${v2%%.*([.0])}) ]]`. Using `%f`, instead of `%d`, solves the leading `0` problem. `${v1%%.*([.0])}` gets rid of trailing `.0`s (requires `shopt -s extglob`). – M. Nejat Aydin Jan 02 '22 at 01:05