Basically, how to achieve this in Bash? Parsing the output of locale
- declare "$(locale | grep ^LC_CTYPE | tr --delete '"')"
- seems yucky, as it involves four separate commands. Remember, just because locale
prints values for most or all variables doesn't mean those variables are actually set! For example, locale
prints LC_CTYPE="en_NZ.UTF-8"
(among other lines) on my machine, but echo "$LC_CTYPE"
prints nothing.
4 Answers
Assuming evaluating the output of locale
is safe (we assume that locale
is not a shell function, alias, or other non-standard utility):
(eval "$(locale)" && printf '%s\n' "$LC_CTYPE")
This sets all the LC_
and associated variables in the subshell environment and outputs the value of the LC_CTYPE
variable. Performing the eval
inside the subshell avoids polluting the parent environment with the LC_
variables.
Example run on my OpenBSD system where I only ever set LANG=C.UTF-8
:
$ (eval "$(locale)" && printf '%s\n' "$LC_CTYPE")
C.UTF-8
If you like to set (and export) the LC_CTYPE
variable to the value reported by locale
without setting other variables:
$ export LC_CTYPE="$(eval "$(locale)" && printf '%s\n' "$LC_CTYPE")"
$ printf '%s\n' "$LC_CTYPE"
C.UTF-8
According to man locale
the value of LC_CTYPE
being enclosed in double quotes signifies it's an "implied value":
Values for variables set in the environment are printed without
double quotes, implied values are printed with double quotes.
which means that LC_CTYPE
is not set and locale
will print the "implied" value which, if I understand correctly, will be the value of LC_ALL
or, if that's not set either, the value of LANG
:
For glibc, first (regardless of category), the environment variable LC_ALL is inspected, next the environment variable with the same name as the category, and finally the environment variable LANG. The first existing environment variable is used
So locale
will use the first value it finds, in this order: LC_ALL
, then LC_CTYPE
and then LANG
Note that LC_ALL
takes precedence if set, so even if LC_CTYPE
was set too (maybe to a different locale) locale
will report the value of LC_ALL
for LC_CTYPE
.
In conclusion, to get the value of LC_CTYPE
that is currently being used by locale
you could run something like
echo ${LC_ALL:-${LC_CTYPE:-$LANG}}
Initially I had thought the goal was to get the actual value of LC_CTYPE
if set and only if it's not set, fall back to what locale
uses, in which case the first two variables have to be swapped:
echo ${LC_CTYPE:-${LC_ALL:-$LANG}}
-
"if I understand correctly, will be the value of LC_ALL or, if that's not set either, the value of LANG." Do you have a reference for that? If that's really the case your solution is the simplest.– l0b0Commented Sep 28, 2023 at 20:19
-
That reads like the precedence is
LC_ALL
> all otherLC_*
variables >LANG
. So it should be${LC_ALL:-${LC_CTYPE:-$LANG}}
?– l0b0Commented Sep 28, 2023 at 22:15 -
1@l0b0 - ok, my bad, I understand now what you meant (I guess my ADHD got the best of me...) I've edited the post. Commented Sep 29, 2023 at 11:44
If you just want to parse the output of locale
and then set the variable, you can just source it directly like this:
. <(locale | grep '^LC_CTYPE=')
That selects only the LC_TYPE
line:
$ locale | grep '^LC_CTYPE='
LC_CTYPE="en_US.UTF-8"
And then you source that, using the .
builtin, into the current shell:
$ echo $LC_CTYPE
$ . <(locale | grep '^LC_CTYPE=')
$ echo "$LC_CTYPE"
en_US.UTF-8
Note that this requires a shell that supports process substitution, such as bash
or zsh
or ksh
and others. If you are not using such a shell, you might need to do something more complicated like
locale | grep '^LC_CTYPE=' > file && . ./file
-
The value could just be output, stripping the assignment, and without the need to write it into a file:
locale | sed -n '/LC_CTYPE/{s/^LC_CTYPE=//;p}'
. Commented Sep 28, 2023 at 13:39 -
@Vilinkameni you mean
LC_CTYPE=$(locale | sed -n '/LC_CTYPE/{s/^LC_CTYPE=//;p}')
? That won't work, you need to remove the quotes else you get an error (setlocale: LC_CTYPE: cannot change locale ("en_US.UTF-8")
). In any case, you wouldn't use something that complicated, even if you had to usesed
, you could dolocale | sed -n 's/^LC_CTYPE=//p'
.– terdon ♦Commented Sep 28, 2023 at 13:43 -
Of course. If setting the variable is intended, the whole thing can be piped through
tr -d '"'
. Somehow I like this answer better than usingeval
though. Commented Sep 28, 2023 at 13:46
Without using any external commands, except for locale
:
while read -r line; do
[[ "${line}" =~ ^LC_CTYPE= ]] && {
export LC_CTYPE="${line#*=}"
break
}
done < <(locale)
-
You don't want
LC_CTYPE*
, the OP needs to match justLC_CTYPE
and nothing else, so you should even change the regex to^LC_CTYPE=
to avoid matching a variable namedLC_CTYPE2
or whatever. Also, you're not setting te variable and I think the OP wants that. Can you explain the benefit of this? It is a tiny bit faster thanlocale | grep -m1 '^LC_TYPE'
, but the difference is negligible (average 0.00251495 seconds for the shel loop vs 0.00286379 for grep over 300 repetitions).– terdon ♦Commented Sep 28, 2023 at 13:26 -
The only benefit I saw was not using external commands. However, I did not take into account the performance difference.– qqqqCommented Sep 28, 2023 at 13:33
eval
the output oflocale
and then see$LC_CTYPE
? I'm not posting this as an answer because I have not determined that doing this is always safe.$LC_CTYPE
variable or the value returned bylocale
? If the latter, islocale | grep -Po 'LC_CTYPE="\K[^"]+'
simple enough or do you need something more portable that doesn't require GNUgrep
? Do you want the variable to be set after your command?LC_CTYPE
variable to be set after whatever command or if you just want to see the value? Any reason yo don't just do. <(locale | grep '^LC_CTYPE=')
for example? Why do you needdeclare
?