2

Say I have a variable var whose value is fOo bar1 baR2 bArab.

How do I go about saving into another variable, say, lc_var, a version of var where all but the first word are converted to lowercase?

I know it can look something like lc_var=$(echo $var | ...), where ... will replaced by an appropriate AWK or sed command.

4 Answers4

4

With the zsh shell:

set -o extendedglob
lc_var=${var/%(#m) */${(L)MATCH}}

Where ${var/%pattern/replacement} like in ksh replaces the end of the var that matches the pattern with the replacement, (#m) causes the matched portion to be stored in $MATCH (that's the part that needs extendedglob) and ${(L)MATCH} converts $MATCH to lowercase.

With the bash shell:

tmp1=${var%% *}
tmp2=${var#"$tmp1"}
lc_var=$tmp1${tmp2,,}

POSIXly, you'd do:

lc_var=$(
  awk '
    BEGIN{
      var = ARGV[1]
      if (i = index(var, " "))
        var = substr(var, 1, i) tolower(substr(var, i + 1))
      print var "."
    }' "$var"
)
lc_var=${lc_var%.}

In any case, that can't look like lc_var=$(echo $var ...) because $(...) is POSIX/Korn/bash/zsh shell syntax, and in those shells, echo can't output arbitrary data and variable expansions should be quoted (except maybe in zsh).

All those variants turn to lower case all the characters past the first space character in $var (making no assumption on what characters they may be). You'd need to adapt it if words are possibly delimited by characters other than space, or if there may be word delimiter characters before the first word.

  • Can you explain how is awk able to read that variable and not give an error like "awk: cannot open fOo bar1 baR2 bArab (No such file or directory)"? Thanks. – seshoumara Jun 10 '19 at 07:10
  • 1
    @seshoumara, awk doesn't process any input when there are only BEGIN statements (and optional function definitions). – Stéphane Chazelas Jun 10 '19 at 07:11
  • Cool! I'm just learning awk. Too bad sed can't do that, my favorite command. I changed my answer to not use echo either, based on that post you linked. I've been using echo in every other line in my bash scripts for years :|, time to use printf then. – seshoumara Jun 10 '19 at 07:28
  • @seshoumara, GNU sed can do it with \L as you've shown (that syntax coming from vi/ex initially IIRC), POSIX sed can do it with y but you'd need to manually specify all the transliterations (AÁÂ -> aáâ...) – Stéphane Chazelas Jun 10 '19 at 07:31
  • I meant sed can't read a variable directly as awk does in your example by checking the arguments passed; sed always needs an input file, or stdin. – seshoumara Jun 10 '19 at 07:37
  • @seshoumara, with GNU sed, there's echo | VAR=$var@ sed 's/^/printenv VAR/e; s/@$//; ...' (on systems without a printenv command, you can replace with echo | VAR=$var 's/^/printf "%s@" "$VAR"/e; s/@$//;...'. POSIXly: printf '%s\n' "$var" | sed -e :1 -e '$!{N;b1' -e '}' -e ... – Stéphane Chazelas Jun 10 '19 at 08:31
3

With bash:

var='fOo bar1 baR2 bArab'
tail=${var#* }; lc_var="${var%% *} ${tail,,}"
echo "$lc_var"

fOo bar1 bar2 barab

as a function:

set_lc(){ declare -n v=$1; declare t=${2#* }; v="${2%% *} ${t,,}"; }

or:

set_lc(){ typeset -n _v=$1; typeset h=${2%% *}; typeset -l t=${2#"$h"}; _v=$h$t; }

(this 2nd version should also work in ksh, and will handle an argument made up of a single word)

set_lc var 'FOO BAR BAZ'
echo "$var"

FOO bar baz

More info about bash's weird and limited parameter expansion modifiers and about declare / typeset in the bash manual.

  • That doesn't work properly if the variable contains only one word (doesn't contain any space character) – Stéphane Chazelas Jun 10 '19 at 05:03
  • The second version will. There are other limitations -- like not working with words separated by tabs, or correctly handling the dotless I in the turkish locale. I'll leave it as is, because the point of the examples was just to demonstrate the use of typeset -l and ${var,,}. –  Jun 10 '19 at 05:55
2

The following solution uses GNU sed and was tested in bash only. It works even if the variable contains multi-line text.

lc_var="$(printf "%s\n" "$var"|sed ':l;$!{N;bl};s:\s.*:\L&.:')"
lc_var="${lc_var%.}"

Many thanks to @Stéphane Chazelas for the discussion about edge cases, like echo, sed -z, command substitution and other things.

seshoumara
  • 862
  • 5
  • 7
  • @StéphaneChazelas Updated my solution to treat some discussed edge cases. Since I need the elegant \L in sed, I require the GNU version anyway, so posix was sacrificed. – seshoumara Jun 10 '19 at 11:05
1
lc_var=$(echo $var | sed 's/^\([^ ]*\)\(.*\)/\1\L\2/g')

(with a little help from http://timmurphy.org/2013/02/24/converting-to-uppercase-lowercase-in-sed/)

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
David Yockey
  • 1,834