Skip to content

Instantly share code, notes, and snippets.

@brin100
Last active August 27, 2015 21:36
Show Gist options
  • Save brin100/e484d36e690aa827ca01 to your computer and use it in GitHub Desktop.
Save brin100/e484d36e690aa827ca01 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# locale-check - find common misconfigurations in system locale
#
# (c) 2012-2015 Mantas Mikulėnas <[email protected]>
# Released under the MIT Expat license.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
warn() { printf "\e[31mwarning:\e[m %s\n" "$*"; ((++n_warnings)); } >&2
note() { printf "\e[33mnotice:\e[m %s\n" "$*"; ((++n_notices)); }
problem() { (( ++sy_total )); printf "\n\e[33mproblem:\e[m %s\n" "$*"; }
is_locale_supported() {
locale -a 2>/dev/null | grep -qsxF "$(get_locale "$1")"
}
get_locale() {
local locale=$1
if [[ $locale == *.* ]]; then
locale=${locale%%.*}.$(get_charset "$locale")
fi
echo "$locale"
}
get_charset() {
local locale=$1 charset=''
if [[ $locale == *.* ]]; then
charset=${locale#*.}
charset=${charset//-/}
charset=${charset,,}
else
charset='default'
fi
echo "$charset"
}
check_setting() {
local name=$1 value=$2
[[ -z $value ]] && return
if ! is_locale_supported "$value"; then
warn "$name: unsupported locale \"$value\""
(( ++sy_missing ))
local xlocale=$(get_locale "$value")
sy_missing_locs["$xlocale"]=y
fi
local charset=$(get_charset "$value")
if [[ $charset != "utf8" ]]; then
warn "$name: non-UTF8 locale \"$value\""
sy_nonutf8_vars["$name"]=y
(( ++sy_nonutf8 ))
fi
if [[ "$charset" != "$main_charset" ]]; then
if [[ "${name##* }" == "LANG" ]]; then
return
fi
warn "$name: charset does not match LANG ($charset | $main_charset)"
(( ++sy_charmismatch ))
fi
}
shopt -s extglob
checking_term=0
checking_parent=0
guessing_parent=""
declare -i pid_shell=$PPID
declare -i pid_term=$(ps -o 'ppid=' $pid_shell)
declare -i pid_parent=$(ps -o 'ppid=' $pid_term)
if (( pid_term > 1 )) && [[ -r "/proc/$pid_term/environ" ]]; then
checking_term=1
if (( pid_parent > 1 )); then
if [[ -r "/proc/$pid_parent/environ" ]]; then
checking_parent=1
fi
elif [[ $SESSION_MANAGER == */tmp/.ICE-unix/+([0-9]) ]]; then
guessing_parent="from \$SESSION_MANAGER"
pid_parent=${SESSION_MANAGER##*/tmp/.ICE-unix/}
if [[ -r "/proc/$pid_parent/environ" ]]; then
checking_parent=1
fi
fi
fi
ps_shell=$(ps -o 'pid=,command=' $pid_shell)
ps_terminal=$(ps -o 'pid=,command=' $pid_term)
ps_parent=$(ps -o 'pid=,command=' $pid_parent)
echo " * Parent: $ps_parent"
echo " * Terminal: $ps_terminal"
echo " * Shell: $ps_shell"
echo ""
unset ${!sy_*}
declare -A sy_missing_locs
declare -A sy_nonutf8_vars
# Read terminal's environment
vars=("LC_CTYPE" "LC_NUMERIC" "LC_TIME" "LC_COLLATE" "LC_MONETARY" "LC_MESSAGES"
"LC_PAPER" "LC_NAME" "LC_ADDRESS" "LC_TELEPHONE" "LC_MEASUREMENT"
"LC_IDENTIFICATION" "LC_ALL")
if (( checking_term )); then
unset ${!TERM_*}
while IFS='=' read -d '' name value; do
if [[ $name == LANG || $name == LC_* ]]; then
declare "TERM_$name"="$value"
fi
done < "/proc/$pid_term/environ"
elif [[ $ps_terminal == *' sshd:'* ]]; then
warn "I cannot check your terminal's settings over SSH, skipping."
else
warn "Terminal's environment is inaccessible, skipping shell=term checks."
fi
if (( checking_parent )); then
if [[ $guessing_parent ]]; then
note "Tried to guess parent process $guessing_parent."
fi
unset ${!PARN_*}
while IFS='=' read -d '' name value; do
if [[ $name == LANG || $name == LC_* ]]; then
declare "PARN_$name"="$value"
fi
done < "/proc/$pid_parent/environ"
elif (( checking_term )); then
warn "Parent's environment is inaccessible, skipping term=parent checks."
else
note "Also skipping term=parent checks."
fi
# Basic checks
if [[ -z $LANG ]]; then
warn "(shell) LANG: not set"
main_charset='default'
(( ++sy_nolang ))
else
main_charset=$(get_charset "$LANG")
fi
if (( checking_term )); then
if [[ -z $TERM_LANG ]]; then
warn "(term) LANG: not set"
t_main_charset='default'
(( ++sy_nolang ))
else
t_main_charset=$(get_charset "$TERM_LANG")
fi
fi
if (( checking_parent )); then
if [[ -z $PARN_LANG ]]; then
warn "(parent) LANG: not set"
p_main_charset='default'
(( ++sy_nolang ))
else
p_main_charset=$(get_charset "$PARN_LANG")
fi
fi
for name in LANG "${vars[@]}"; do
if [[ "$name" == "LC_COLLATE" ]]; then
# skip LC_COLLATE on purpose, having it as 'C' is fine
continue
fi
value=${!name}
locale=$(get_locale "$value")
charset=$(get_charset "$value")
(( checking_term )) && {
t_name="TERM_$name"
t_value=${!t_name}
t_locale=$(get_locale "$t_value")
t_charset=$(get_charset "$t_value")
}
(( checking_parent )) && {
p_name="PARN_$name"
p_value=${!p_name}
p_locale=$(get_locale "$p_value")
p_charset=$(get_charset "$p_value")
}
check_setting "(shell) $name" "$value"
(( checking_term )) &&
check_setting "(term) $name" "$t_value"
(( checking_parent )) &&
check_setting "(parent) $name" "$p_value"
if [[ "$name" == "LC_ALL" && -n "$value" ]]; then
warn "$name: should not be set ($value)"
(( ++sy_lcall ))
fi
(( checking_term )) && {
if [[ -n "$value" && -z "$t_value" ]]; then
warn "$name: set by shell but not terminal ($value | none)"
(( ++sy_mismatch ))
elif [[ -z "$value" && -n "$t_value" ]]; then
warn "$name: set by terminal but not shell (none | $t_value)"
(( ++sy_mismatch ))
elif [[ "$charset" != "$t_charset" ]]; then
warn "$name: charset mismatch between shell and terminal ($locale | $t_locale)"
(( ++sy_mismatch ))
elif [[ "$locale" != "$t_locale" ]]; then
warn "$name: lang mismatch between shell and terminal ($locale | $t_locale)"
(( ++sy_mismatch ))
fi
}
(( checking_parent )) && {
if [[ -n "$t_value" && -z "$p_value" ]]; then
warn "$name: set by terminal but not parent ($t_value | none)"
(( ++sy_p_mismatch ))
elif [[ -z "$t_value" && -n "$p_value" ]]; then
warn "$name: set by parent but not terminal (none | $p_value)"
(( ++sy_p_mismatch ))
elif [[ "$t_charset" != "$p_charset" ]]; then
warn "$name: charset mismatch between terminal and parent ($t_locale | $p_locale)"
(( ++sy_p_mismatch ))
elif [[ "$t_locale" != "$p_locale" ]]; then
warn "$name: lang mismatch between terminal and parent ($t_locale | $p_locale)"
(( ++sy_p_mismatch ))
fi
}
done
if [[ ${LANG,,} == *'.utf8' ]]; then
(( ++sy_utf8_dash ))
fi
# Display final results
if (( sy_nolang )) && [[ $LANG && -z $TERM_LANG ]]; then
problem "Your terminal is missing \$LANG in its environment."
echo " * Locale variables must be set for the terminal emulator itself"
echo " (and for the entire session), not only for the shell."
elif (( sy_nolang )) && [[ -z $LANG && $TERM_LANG ]]; then
problem "Your shell is missing \$LANG in its environment."
echo " * Even though your terminal has the correct \$LANG ($TERM_LANG),"
echo " it was removed by your shell's .profile, .bashrc or similar files."
elif (( sy_nolang )); then
problem "You do not have \$LANG set."
echo " * It must be set to a <lang>.utf-8 locale."
fi
if (( sy_mismatch )); then
problem "Shell and terminal have different locale settings."
echo " * Your .bashrc or similar startup scripts may be overriding them."
fi
if (( sy_p_mismatch )); then
: ${parn_locale:=$PARN_LC_ALL}; : ${parn_locale:=$PARN_LC_CTYPE}
: ${parn_locale:=$PARN_LANG}; : ${parn_locale:=empty}
: ${term_locale:=$TERM_LC_ALL}; : ${term_locale:=$TERM_LC_CTYPE}
: ${term_locale:=$TERM_LANG}; : ${term_locale:=empty}
problem "Terminal and its parent have different locale settings."
echo " * Your session doesn't have the right locale set, and your window manager"
echo " is launching all programs using the $parn_locale locale. But your terminal"
echo " hides the problem by setting its own locale to $term_locale."
echo " * Fix your system to set the locale at login or session startup time."
fi
if (( sy_lcall )); then
problem "You have \$LC_ALL set; it overrides all other settings."
echo " * Do not set \$LC_ALL unless absolutely required."
echo " For normal usage, setting \$LANG should be enough."
fi
if (( sy_nonutf8 )); then
problem "Your current locale is using a legacy charset."
echo " * The incorrect variables are:"
for var in "${!sy_nonutf8_vars[@]}"; do
varname=${var#* }
printf ' - %-20s (currently "%s")\n' "$var" "${!varname}"
done
echo " * Change your locales to their UTF-8 variants."
fi
if (( sy_charmismatch )) && [[ $LANG ]]; then
problem "Your locale settings use different charsets."
echo " * If any \$LC_* variables are set, they should use the same charset as \$LANG."
fi
if (( sy_missing )); then
problem "Your current locale is missing from the system."
echo " * The missing locales are:"
printf ' - \e[1m%s\e[m\n' "${!sy_missing_locs[@]}"
echo " * Make sure /etc/locale.gen has the apropriate lines uncommented."
echo " After editing the file, run 'locale-gen' as root."
fi
if (( sy_utf8_dash )); then
problem "\$LANG is missing a dash in the charset."
echo " * Even though 'utf-8' and 'utf8' are equivalent on Linux, the latter isn't"
echo " recognized by BSDs, nor by some poorly written programs."
echo " * Change \$LANG from \"$LANG\" to \"${LANG%.*}.UTF-8\""
fi
if (( n_warnings + n_notices )); then
echo ""
fi
if (( sy_total > 0 )); then
echo -e "\e[1m$sy_total problems found.\e[m Here's a quick UTF-8 test for you: --> \xe2\x98\x85 <--"
elif (( !checking_term )); then
echo -e "Looks good, but you still need to check your terminal: --> \xe2\x98\x85 <--"
else
printf "Looks good. \xe2\x99\xa5\n"
echo " * You are using the $LANG locale."
echo " * Shell's locale matches terminal's locale."
if (( checking_parent )); then
echo " * Terminal's locale matches parent process locale."
else
echo " * Could not check if terminal and parent's locale settings match."
fi
fi
if (( sy_total > 0 || !checking_term )); then
echo " * a star -- font and terminal are okay."
echo " * 3 question marks -- your terminal does not correctly interpret UTF-8".
echo " * a box or rectangle -- UTF-8 works fine, but you need a better font."
echo " * empty area -- you ${B}really${R} need a better font or something."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment