4

I'm looking for something which runs under macOS that will let me do the following:

Suppose I have a file called "program.list" with contents something like this:

"Program 1" "/usr/local/bin/program 1"
"Program 2" "/usr/local/bin/program 2"
"Program 3" "/usr/local/bin/other-program xyz"
"Program 4" "/usr/local/bin/something-else"
... etc. ...

I'd like to feed this file to a program which, for example, could be called "run-from-list", and which I could invoke as follows:

run-from-list program.list

... and when that command is invoked, the following would show up on the screen in a scrollable list:

Program 1
Program 2
Program 3
Program 4
... etc. ...

And whatever line I click on, the associated program will run. For example, if I click on "Program 1", it will run the "/usr/local/bin/program 1" program.

Is there any way to write "run-from-list" in MacOS without compiling source code into an executable? A scripting language would be suitable. Or is there some sort of existing utility that I could download which I could use similarly for this purpose?

5
  • Have you tried creating a Shortcut?
    – Allan
    Apr 16 at 2:20
  • try using the Automator app
    – Ruskes
    Apr 16 at 3:15
  • For a programmatic solution, AppleScript has a choose from list that can be used to get a selection. The selection could then be used to run its matching application, executable, script, etc. Depending on how you are wanting to run it, the script can be saved as an application, an Automator Quick Action, an item in the Scripts Menu, etc.
    – red_menace
    Apr 16 at 4:31
  • 2
    Forgive me, but isn't this what the Dock is for?
    – cmason
    Apr 16 at 13:51
  • Thank you to all of you. Some quick responses. A shortcut is a desktop contstruct. I don't want this to run on the desktop, but rather, via a list of text items that shows up exactly as I described in my question. The same goes for Dock. It doesn't show the selectable items as text in the way that I described above. I haven't figured out how to use Automator for this, but I will dig deeper. As for AppleScript "choose from list", I will check that out, also. Thank you again to all.
    – HippoMan
    Apr 24 at 23:03

7 Answers 7

2

I combined zsh and AppleScript to accomplish this. Doesn't need compilation.


My version of program.list looks like this:

➜  ~ cat program.list

"Music" "open -a Music"
"Safari" "open -a Safari"
"New Safari" "open -na Safari"
"A Good Question" "open -a Safari https://apple.stackexchange.com/q/471930/71941"

I created a run_from_list.sh in my home dir and execute chmod +x run_from_list.sh.


The contents of run_from_list.sh are as follows. Code is adequately commented for every relevant step:

#!/bin/zsh

# Check if the input file exists.
if [[ ! -f "$1" ]]; then
    echo "File not found!"
    exit 1
fi

# Initialize arrays to read the program names and commands into.
program_names=()
program_commands=()

# Read and process the input file line by line.
while IFS= read -r line; do
    program_name=$(echo "$line" | cut -d '"' -f 2)
    program_command=$(echo "$line" | cut -d '"' -f 4)
    program_names+=("$program_name")  # Append the program name.
    program_commands+=("$program_command") # Append the prescribed command for the program.
done < "$1"

# Generate AppleScript command to display list.
cmd="choose from list {"
for name in $program_names; do
    cmd+='"'"$name"'", '
done
cmd="${cmd%,*} } with title \"Select a Program\""

# Run AppleScript command and capture the selection.
selected_program=$(osascript -e "$cmd")

# User made the selection. Now, find and execute the corresponding command.
for i in {1..$#program_names}; do
    if [[ "${program_names[i]}" == "$selected_program" ]]; then
        echo "Running ${program_commands[i]}" # Display a message on the terminal.
        eval "${program_commands[i]}" # Execute the prescribed command.
        break
    fi
done

Test it:

  • Go to your home dir by doing cd ~;
  • ./run_from_list.sh program.list.
2
  • Very nice - I appreciate the comments and clear implementation.
    – bmike
    Apr 17 at 1:03
  • Actually, this works great for me! It implements exactly what I asked for. I'm a long-time zsh user, which is another reason that I like it. Thank you very much! I'm accepting this as my answer.
    – HippoMan
    Apr 24 at 23:26
7

You can use the "Script Editor" app on your Mac (located in the Applications/Utilities folder) to do this sort of thing. It lets you write scripts in either AppleScript or JavaScript.

Using JS, something like this should do practically what you want:

const app = Application.currentApplication();
app.includeStandardAdditions = true;

function readFile(file) {
  return app.read(Path(file.toString()));
}

const file = readFile(app.chooseFile());
const paths = JSON.parse(file);

const choices = Object.keys(paths);
const result = app.chooseFromList(choices, {withPrompt: "Select an application:"});

if (result !== false) {
  const [choice] = result;
  
  const app = Application(paths[choice]);
  app.activate();
}

When run, this opens a file dialogue prompting for the file, and then opens a choose from list dialogue, before then opening the selected application. I used a JSON file to keep things easy, but you could certainly adapt it to your custom format if needed.

Here's the result when run with a file like

{
  "Calendar": "/System/Applications/Calendar.app",
  "Chrome": "/Applications/Google Chrome.app"
}

There are many other ways to open files:

  • You can read them in as an argument from the shell;
  • You can make the script a "droplet", and then drop the files onto it to launch it with those files;
  • You can hardcode a path to specific files;
  • etc

Check out the Working with Files and Folders docs to see how you might be able to do that.

1
  • This looks great, and it's likely to be my accepted Answer, after I have some time to run the test. Thank you!
    – HippoMan
    Apr 24 at 23:05
6

This may not be exactly what you want, but I think it's close.

  • I created a directory called "Run_From_List."
  • In it, I placed soft links or aliases to several programs.
  • Used Finder to drag the directory to the Dock to the right of the divider.
  • set the directory's options in the Dock to "View content as Fan" and "Display as Stack."

One click on the Dock icon displays the contents as the image below.  Then one click on an app symlink launches the application.  Doesn't have to be an app bundle—the jar file in mine automatically invokes the JVM along with it, and if the link is to an executable shell script, it would run the script in a new Terminal window.

If the directory contains too many program links to fit in the "Fan" format, try the "Grid" format.  Or use "Automatic" which chooses Fan or Grid according to the size. enter image description here

3
  • 1
    "Display as List" makes something that looks like an ordinary menu.
    – Barmar
    Apr 16 at 14:24
  • This doesn't do anything to generate the program links from the file input. Manual intervention is required.
    – ojchase
    Apr 16 at 22:34
  • Well, I said it was close. I could easily write a script to create the symlinks from the file, if the folder is already in the dock. For a little more work, the script can update the Dock’s plist to add the folder.
    – WGroleau
    Apr 16 at 23:01
4

Another answer uses fzf as a component of a larger bash script. This answer uses fzf as a self-contained one-liner.

fzf is a command-line fuzzy finder which does pretty much exactly what you want. You pipe text into stdin, select a line from the list (supports double-click with mouse, or you can search or click the line you want, then press 'Enter'.)

You can customize it to only show the first field, and can hook up custom actions to keybindings. Here's an example. I've changed your program.list file to be a tab separated values file for simplicity. You can use a different separator if you prefer, just update the --delimiter argument below.

Contents of program.list:

Neovim  nvim
Visidata        vd
Htop    /usr/bin/htop

Note that the columns are separated by a single tab character. Note also, that we are not required to specify the full path to the program if it is on our PATH (but we can if we want to).

The following fzf command is all we need for the full functionality required:

fzf < program.list --delimiter '\t' --with-nth 1 --bind 'enter:execute({2})+abort,double-click:execute({2})+abort'

Run program from list

When you're happy with the behavior of the fzf command, simply wrap it up in a shell script, make it executable, and put it somewhere handy.

Explanation of the fzf arguments:

< program.list   # pipe program.list file to fzf stdin
--delimiter '\t' # split the input into columns using tab delimiter
--with-nth 1     # only show the first column in the selection list

# Execute the selected value from the second column then quit, when either `Enter` or `Double-click` is received.
--bind 'enter:execute({2})+abort,double-click:execute({2})+abort'

Edit: If you want to support running commands with arguments, update the command as follows:

fzf < program.list --delimiter '\t' --with-nth 1 --bind 'enter:execute(sh -c {2})+abort,double-click:execute(sh -c {2})+abort'
3
  • A similar GUI option is dmenu-mac, inspired by dmenu from Linux. (I haven't tried the Mac version.)
    – daviewales
    Apr 17 at 3:50
  • Neat having this as a self-contained 1-liner! But with fzf's builtin execute binding I don't think it's possible to do multi-word commands (with args), much less evaluated commands like the ones given in my example.
    – luckman212
    Apr 19 at 2:55
  • 1
    I've added a tweaked version which supplies the given command as an argument of sh -c, which makes it work for commands with arguments.
    – daviewales
    Apr 19 at 4:26
3

Here's a solution for use in a Terminal. Since your question talks about running console programs from /usr/local/bin and you mention invoking, I believe you want to run this from a shell vs. clicking an icon in the Finder or Dock.

Link to the GitHub repo: https://github.com/luckman212/click-run

This is a quick PoC, written in plain Bash. It does have one dependency (fzf) for the nice clickable/filterable list view. That could be eliminated if necessary by using a builtin such as select instead.

It comes with a sample configuration file, which is a tab-separated list in the format:

command1_name(literal tab)command1_args
command2_name(literal tab)command2_args
...

This format was chosen specifically so your command args could contain spaces, reference variables ($foo), or use pipes (|) and other shell specials. There's also a --dry-run flag you can pass to preview commands before execution (to avoid surprises and gotchas possible when using eval).

2

Many apps exist for this purpose - a search for "launcher" on the Mac App Store brings up several utilities that might do what you want, depending on your exact needs and purposes.

Any script that needs to define and control an interface is likely to be a similar complexity to that of a compiled app.

Interestingly, Classic Mac OS used to have a Launcher app that was entirely configurable. Another lost feature...!

enter image description here

3
  • 2
    The answer posted by @wgroleau basically recreates the Launcher app :-)
    – nohillside
    Apr 16 at 7:32
  • 2
    @nohillside ... S'pose so, but it's not the same.... ;-)
    – benwiggy
    Apr 16 at 7:51
  • 1
    I have fond memories of the Launcher losing its apps and configuration at random. I figured out that if I locked the Launcher app, it would stay intact. When I was managing a fleet of Macs, the Launcher problem was a big PITA until I 'fixed' it. Those were the days.
    – IconDaemon
    Apr 16 at 11:36
1

Another GUI way is Xmenu also on the AppStore

One of the menus is custom which you create by making links in ~/Library/Application Support/XMenu/Custom to the programs.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .