How Can I Invoke Shell Command `which`?

Hence the "Yuck!" :wink:

Which means people will have to install Haskell, which (presumably, I've not done it) means they'll also have ~/.ghcup/bin/runhaskell. So I suggest you test for that, use it if it's there, and throw a "Please locate..." prompt if it isn't.

1 Like

Yes, my previous version is this:

Execute a Haskell Script with Arguments - Plug In Actions - Keyboard Maestro Discourse

Could you tell me what do you mean by that ? The plug-in attempts to run the Haskell source code passed as argument.

The problem is many users use Stack, other use ghcup, etc...many things can go wrong, so I would like to find the executable in the system, if possible.

Then check for the file in the places you know it might be, then ask the user if you can't find it.

Trawling even just their user folder with find could take a while, and will have an impact on their machine's performance. locate would be faster but, as said earlier, is unlikely to work as few people will have a pre-built database. mdfind won't work when, like yours, runhaskell is under a . directory.

Or maybe use an AppleScript in KM to fire up a Terminal window and run which in that -- running in the user's "normal" space it'll have PATH as it was set by the haskell install, so should give you the right answer. Again, disruptive -- so worth popping an information dialog to let the user know what's happening and why.

Thank you for those good ideas ! I’ll experiment.

I don't use Haskell, so it was unclear to me that runhaskell was something installed by Haskell and not you yourself.

Like @Nige_S said – you can quickly test for the usual suspects and if nothing is found make the user manually select the file.

I would use an Execute a Shell Script action for this, because it will be faster than KM native actions for testing multiple locations.

#!/usr/bin/env zsh

if   [ -f ~/.bashrc ]; then
 	printf "%s~/.bashrc"
elif [ -f ~/.bash_profile ]; then
 	printf "%s~/.bash_profile"
elif [ -f ~/.profile ]; then
   printf "%s~/.profile"
else
   printf 'Nothing found!'
fi
1 Like

That's great, @ccstone ! I'll experiment with that shell script !

1 Like

My idea is this one: I would like to search the path in each of those locations. To accomplish that, I would need to read the file, and obtain the list of comma separated values of the PATH variable in each of those paths.

Do you think that is possible ?

That would be my first step. If nothing is found, then they user should enter the path.

Your suggestion is better than searching the whole drive for the executable, I think. That was one of my first thoughts.

In a way, I should reimplement which, I think.

which searches through the locations in the user's $PATH, so to reimplement it you'd have to get their $PATH -- by which time you don't need which!

So just do what you need in the user's environment. Something like this -- the enabled AppleScript action will get you the full runhaskell path. Disable that and enable the one below and you'll get their full $PATH, useful if your macro is using other Unixy bits and pieces.

Get Path with Terminal.kmmacros (9.3 KB)

Image

Not fully tested as I wasn't going to quit Terminal and lose all my open windows! But works with open-new-window-then-close-it.

2 Likes

Thank you so much !

Yes, you’re right.

My previous post was more accurate. I am trying to find a way to extract the PATH variable in ~/.bashrc for example and use that information to find the executable.

Since there are a number of possible environmental set-up files, I think employing the user's Terminal to acquire their path is a pretty good way to go.

Another possibility is to locate the appropriate set-up file:

find ~ -maxdepth 1 -type f -iname "*profile*"

And then use source to load it and read the resultant shell variables.

When I know what file I'm dealing with I use source – otherwise I use the Terminal method.

1 Like

Thank you so much, @ccstone ! I would like to experiment with something like this.

In my case, that command returns:

~/.zprofile

and the contents are:

# Set PATH, MANPATH, etc., for Homebrew.
eval "$(/opt/homebrew/bin/brew shellenv)"
# Set PATH, MANPATH, etc., for Homebrew.
eval "$(/opt/homebrew/bin/brew shellenv)"

However, my $PATH variable is in ~/.zshrc
and one of the lines is:

export PATH=$PATH:~/.ghcup/bin/:/opt/homebrew/Cellar/

I would like to obtain the paths there, for example. Do you think that is possible ?

In which way are you able to use that command to read shell variables ?

The complexity here is that you have to be aware of all the possible places the user's $PATH might have been changed, and there are quite a few options.

For instance my set-up is all in my ~/.profile file, so I don't have to monkey with different set-ups for different shells.

I used to have a pretty comprehensive list, but it's lost to time – so you'll have to do your own research. Here's a start:

Google is your friend – as is the voluminous StackExchange.

In short source loads the config file you feed just as if you started a new shell.

So – with no assigned ENV_PATH variable in Keyboard Maestro I get this from an execute-shell-script-action:

echo $PATH
/usr/bin:/bin:/usr/sbin:/sbin

But since I know my changes occurred in ~/.profile I can do this:

Get Path from ~-.profile.kmmacros (1.9 KB)
Keyboard Maestro Export

And voila! My $PATH is now:

/Users/chris/Library/Python/3.9/bin:/opt/local/bin:/opt/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin

The same as in my Terminal.

You have your work cut out for you if you really want to pursue this route, as there are many possible config files and not necessarily just one per each shell variant.

I haven't researched this at length in years, and it was a royal pain when I did it originally – many, many hours spent...

2 Likes

Thank you so much ! I really appreciate your comments, example and advice.

Could you share here the contents of ~/.profile, if that is possible ? Or at least the kind of structure it has ?

An SO answer says:

source is a Bash shell built-in command that executes the content of the file

It doesn't say that reads the contents of the file. Do you think execution and reading are interchangeable terms in this scenario ?

What are the contents/structure of your ~/.profile? Then ask yourself if it actually matters -- what shell will your users be using? Will ~/.profile be relevant?

Good question.

I am just trying to understand the example he gave. Are there a list of paths, eval commands, etc ?

There are many lists etc. But my point is -- "What shell is the user using?" There's little point in understanding ~/.profile if they are on (the now default) zsh.

You're jumping in at the middle. Start with the "Shell Setup Files" Chris linked to above, understand that each shell is different, understand that the path to runhaskell may not even be there and they call it explicitly every time. To cover every possible situation is a) a lot of work for you, and b) intrusive on the user.

What can you do to reduce that burden? How many "versions" beyond Stack and gchup are there, and do you really need to go beyond those two plus "ask the user" if they've used something else?

2 Likes

Thank you @ccstone and @Nige_S for all your help. I've learned a lot.

I've settled (for the time being) on the following solution. In the plug-in, I added a parameter Path where the user can place the path to runhaskell executable:

If that parameter is blank, then I would run the following shell script inside the plug-in which tests common locations where the executable could be found:

#!/usr/bin/env zsh
declare -a paths=(
    ~"/.ghcup/bin"
    ~"/usr/bin"
    ~"/usr/local/bin"
)

for i in "${paths[@]}"
do
    RUNHASKELLPATH="$i/runhaskell"
    if [ -f $RUNHASKELLPATH ]; then
        echo $RUNHASKELLPATH
    fi
done
2 Likes