Home > Software engineering >  Bash script not exiting with "exit" without subshells (I think)
Bash script not exiting with "exit" without subshells (I think)

Time:04-14

I'm using stow to manage my dotfiles and I'm writing a script that can set up my pc and packages automatically. I have all my config files in a config folder and that's the stow folder. What I'm basically doing is get the names of all folders (packages) in that folder and stowing them to my home directory. I also have a function that simply tells me if there was an error and exits the script.

I'm running this script on Arch Linux, recently installed, on the tty (I haven't installed a window manager yet). And when it goes to stow bash, it fails because .bashrc already exists in home. It gives me the error message, but it doesn't exit and I can't find the reason. I don't think I'm running the error function on a subshell like I've seen from other people with this problem, unless there's something I'm missing here...

Here's the function:

error () {
    echo "!! ${1} !!"
    exit 1
}

And then later on I have something like this:

ls -1 config | while read line; do
    stow -d config -t ~ -R "${line}" || error "Failed to stow ${line}."
done

Here's the whole code from the creation of the function to the stowing:

step () {
    echo "> ${1}"
}

substep () {
    echo "--> ${1}"
}

error () {
    echo "!! ${1} !!"
    exit 1
}

success () {
    echo "** ${1} **"
}

commandexists () {
    # Check if a command exists
    command -v $1 &> /dev/null
    if [[ $? -eq 0 ]]; then
        return 0
    else
        return 1
    fi
}

pkg-install () {
    pkg="${1:?"Error: pkg parameter unset."}"

    step "${msg_install}: ${pkg}"

    substep "Installing ${pkg} from the Arch Linux Repositories."
    sudo pacman -Sy --noconfirm --needed "$pkg"

    if [[ $? -ne 0 ]]; then
        substep "Installing ${pkg} from the Arch User Repositories."
        yay -Sy --noconfirm --needed "$pkg" || error "${msg_fail_install}: ${pkg}"
    fi

    success "${msg_success_install}: ${pkg}"
}

# Stop installation if yay is not installed
commandexists yay || error "Yay is not installed! Please install yay and try again."

# stow
pkg-install stow

step "Stowing config files"
ls -1 config | while read line; do
    substep "Stowing ${line}"
    stow -d config -t ~ -R "${line}" || error "Failed to stow ${line}."
done
success "Successfully stowed all config files"

As you can see, before stowing it checks if yay is installed by checking if the command exists. If not, it gives me an error and exits. When I run this on my other pc that doesn't have yay installed, it works as expected. It tells me yay is not installed and stops there. But why does it ignore the exit command in the stow part when I run it on the pc that has yay installed?

CodePudding user response:

In this particular case, you can keep your while loop in the parent shell, while moving the ls into a subshell:

while IFS= read -r line; do
    stow -d config -t ~ -R "${line}" || error "Failed to stow ${line}."
done < <(ls -1 config)

(Using IFS= makes your code work correctly with names that begin or end with whitespace; using -r makes it work correctly for names that contain literal backslashes).


...but don't do that in practice; ls output is unsuited to programmatic use.

Instead, use a glob:

for path in config/*; do
    [[ -e $path || -L $path ]] || continue # detect case where config/ was empty
    file=${path%config/}                   # strip the config/ off the name
    stow -d config -t ~ -R "$file" || error "Failed to stow $file"
done
  • Related