1

I have a dotfile repo with setup scripts for my various machines and one of these installs all the relevant apps using Homebrew. But there is a bug in the loop I cannot figure out ... the loop over the list of apps to install seems to work perfectly if replacing brew install $APP with a no-op debug statement such as echo brew install $APP:

$ ./setup.sh 
Installing local apps using Homebrew ...
brew install postgresql
brew install pspg
brew install reattach-to-user-namespace
brew install terraform
brew install the_silver_searcher
brew install tmux
brew install vagrant
finished 

If removing the echo bit to actually invoke that command, the loop stops short after the first successful iteration. The exit code is 0, so no errors arise. This essentially means I only install one app for each time I execute the install function ...

To me, it seems as if Homebrew somehow messes with the state of the loop somehow? See how the contents of the outer bash loop somehow ends up interleaved within the Homebrew output bug

The relevant bits of the shell script is below: a throwaway function that is then immediately invoked.

setup function that installs Homebrew apps

function _f(){ # create throw-away function to not pollute global namespace with local variables
    blue "Installing local apps using Homebrew ...\n"
    brew tap shivammathur/php

    # Creates lines of "APP my/cask/APP"
    local app_to_formula_map=$( awk -F/ '{  print ( ($3 != "") ? $3 : $1) "\t" $0 } ' < apps.local | sort )
    local to_install=$(awk -F'\t' '{  print $1 }' <(printf "%s\n" "$app_to_formula_map"))
    local formulae=$(brew list --formulae -1)
    local casks=$(brew list --casks -1)
    local installed=$(printf '%s\n%s\n' "$casks" "$formulae" | sort)
    local not_installed=$(comm -23 <(printf '%s\n' "$to_install") <(printf '%s\n' "$installed" ) )
    while read APP; do 
        if [ "$APP" == "" ]; then continue; fi
        local formula=$(awk -v APP=$APP -F'\t' '$1==APP{print $2}' <(printf "%s\n" "$app_to_formula_map" ) )

        # This will only be invoked once!
        brew install "$formula"
        # Replace with this to have the loop go more times
        # echo brew install "$formula"

    done <<< "$not_installed"
    echo "finished \n"
}; _f

apps.local

cmake
cowsay
direnv
fortune
fswatch
git-extras
gnupg
homebrew/cask/macvim
htop
hub
jq
maven
neovim
node
shivammathur/php/php@7.4
postgresql
pspg
reattach-to-user-namespace
the_silver_searcher
terraform
tmux
vagrant
oligofren
  • 20,744
  • 16
  • 93
  • 180
  • 1
    Why don't you just use https://docs.brew.sh/Manpage#bundle-subcommand? – jonrsharpe Jan 05 '23 at 07:39
  • Thanks for the tip, but the answer is probably "because the docs are shit?". They refer to a Brewfile without explaining (or linking to) how it looks or how one gets hold of it anywhere on that page and basically only communicates to the ones with existing intimate knowledge of Brew concepts and terms. I do get it probably is supposed to cover some sort of package manager approach for Homebrew a la NPM, but I guess there is a reason for me not coming across other references to it before now :) Still, I will try to google up some more info, so will use this tip going forward! – oligofren Jan 09 '23 at 21:39

1 Answers1

2

The whole while loop body will consume <<< "$not_installed" as stdin. I suspect brew install "$formula" also reads stdin and it consumes all the remaining lines so next read APP would get nothing.

So you can try this:

while read APP; do
  ...

  brew install "$formula" < /dev/null
  #                       ^^^^^^^^^^^

  ...
done <<< "$not_installed"
pynexj
  • 19,215
  • 5
  • 38
  • 56