背景
node
を介してnvm
をインストールするスクリプトを作成しました。
問題
install_latest_stable_node()
内のコマンド_. $LOCAL_Shell_CONFIG_FILE
_は実行されないため、node
およびnpm
(echo "$(which node)" && echo "$(which npm)"
)のPATHを出力すると、 2行の空白行を印刷します。
スクリプトが終了したら、_. ~/.bashrc
_を実行してから、echo "$(which node)" && echo "$(which npm)"
を実行します。これにより、今度は2行の空白行なしでPATHがエコーバックされます。
どうして?
コード
_#!/bin/bash
ask_for_Sudo() {
# Ask for the administrator password upfront.
Sudo -v &> /dev/null
# Update existing `Sudo` time stamp
# until this script has finished.
#
# https://Gist.github.com/cowboy/3118588
while true; do
Sudo -n true
sleep 60
kill -0 "$$" || exit
done &> /dev/null &
}
show_spinner() {
local -r FRAMES='/-\|'
# shellcheck disable=SC2034
local -r NUMBER_OR_FRAMES=${#FRAMES}
local -r CMDS="$2"
local -r MSG="$3"
local -r PID="$1"
local i=0
local frameText=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Note: In order for the Travis CI site to display
# things correctly, it needs special treatment, hence,
# the "is Travis CI?" checks.
if [ "$TRAVIS" != "true" ]; then
# Provide more space so that the text hopefully
# doesn't reach the bottom line of the terminal window.
#
# This is a workaround for escape sequences not tracking
# the buffer position (accounting for scrolling).
#
# See also: https://unix.stackexchange.com/a/278888
printf "\n\n\n"
tput cuu 3
tput sc
fi
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Display spinner while the commands are being executed.
while kill -0 "$PID" &>/dev/null; do
frameText=" [${FRAMES:i++%NUMBER_OR_FRAMES:1}] $MSG"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Print frame text.
if [ "$TRAVIS" != "true" ]; then
printf "%s\n" "$frameText"
else
printf "%s" "$frameText"
fi
sleep 0.2
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Clear frame text.
if [ "$TRAVIS" != "true" ]; then
tput rc
else
printf "\r"
fi
done
}
execute() {
local -r CMDS="$1"
local -r MSG="$2"
local -r TMP_FILE="$(mktemp /tmp/XXXXX)"
local exitCode=0
local cmdsPID=""
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# If the current process is ended,
# also end all its subprocesses.
set_trap "EXIT" "kill_all_subprocesses"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Execute commands in background
eval "$CMDS" \
&> /dev/null \
2> "$TMP_FILE" &
cmdsPID=$!
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Show a spinner if the commands
# require more time to complete.
show_spinner "$cmdsPID" "$CMDS" "$MSG"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Wait for the commands to no longer be executing
# in the background, and then get their exit code.
wait "$cmdsPID" &> /dev/null
exitCode=$?
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Print output based on what happened.
print_result $exitCode "$MSG"
if [ $exitCode -ne 0 ]; then
print_error_stream < "$TMP_FILE"
fi
rm -rf "$TMP_FILE"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
return $exitCode
}
set_trap() {
trap -p "$1" | grep "$2" &> /dev/null \
|| trap '$2' "$1"
}
print_error_stream() {
while read -r line; do
print_error "↳ ERROR: $line"
done
}
print_error() {
print_in_red " [✖] $1 $2\n"
}
print_success() {
print_in_green " [✔] $1\n"
}
print_in_red() {
print_in_color "$1" 1
}
print_in_green() {
print_in_color "$1" 2
}
print_in_purple() {
print_in_color "$1" 5
}
print_in_color() {
printf "%b" \
"$(tput setaf "$2" 2> /dev/null)" \
"$1" \
"$(tput sgr0 2> /dev/null)"
}
print_result() {
if [ "$1" -eq 0 ]; then
print_success "$2"
else
print_error "$2"
fi
return "$1"
}
fix_dpkg() {
declare -a files=("/var/lib/dpkg/lock" "/var/cache/apt/archives/" "/var/cache/apt/archives/lock")
for i in "${files[@]}"
do
# If there is a dpkg lock, then remove it.
if [ -e "$i" ]; then
Sudo rm -rf "$i" &> /dev/null
fi
done
}
install_package() {
declare -r PACKAGE="$2"
declare -r PACKAGE_READABLE_NAME="$1"
if ! package_is_installed "$PACKAGE"; then
fix_dpkg
execute "Sudo apt-get install --allow-unauthenticated -qqy $PACKAGE" "$PACKAGE_READABLE_NAME"
# suppress output ─┘│
# assume "yes" as the answer to all prompts ──┘
else
print_success "$PACKAGE_READABLE_NAME"
fi
}
package_is_installed() {
dpkg -s "$1" &> /dev/null
}
install_nvm() {
# Install `nvm` and add the necessary
# configs in the local Shell config file.
# One-liner:
# wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | PROFILE=~/.bash.local bash && . ~/.bash.local
execute \
"wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | \
PROFILE=$LOCAL_Shell_CONFIG_FILE NVM_DIR=$NVM_DIRECTORY bash" \
"nvm (install)"
}
update_nvm() {
execute \
"cd $NVM_DIRECTORY \
&& git fetch --quiet Origin \
&& git checkout --quiet \$(git describe --abbrev=0 --tags) \
&& . $NVM_DIRECTORY/nvm.sh" \
"nvm (upgrade)"
}
install_latest_stable_node() {
# Install the latest stable version of Node
# (this will also set it as the default).
# One-liner:
#. ~/.bash.local && nvm install node
. $LOCAL_Shell_CONFIG_FILE
execute \
"source $LOCAL_Shell_CONFIG_FILE \
&& nvm install node" \
"nvm (install latest Node)"
}
main() {
ask_for_Sudo
touch "$LOCAL_Shell_CONFIG_FILE"
print_in_purple "\n Dependencies\n\n"
install_package "git" "git"
print_in_purple "\n nvm\n\n"
if [ -d "$NVM_DIRECTORY" ]; then
rm -rf "$NVM_DIRECTORY"
fi
install_nvm
update_nvm
install_latest_stable_node
print_in_purple "\n PATHs\n\n"
echo "$(which node)"
echo "$(which npm)"
}
declare -r LOCAL_Shell_CONFIG_FILE="$HOME/.bashrc"
declare -r NVM_DIRECTORY="$HOME/.nvm"
main
_
スクリプトis指定されたファイルを調達します。
予期されたパス名の代わりに2つの空白行が表示される理由は、スクリプトがバックグラウンドでコマンドを実行する「execute()
」関数内から$LOCAL_Shell_CONFIG_FILE
を取得するためです(つまり、サブシェル内)。
サブシェルは、親シェルの環境を変更できません。