10

As an example, say I have:

One, Two.
OnE, Two.
ONE, TWO.

And I want to replace only the lines with all upper-case with lower case so that I get:

One, Two.
OnE, Two.
one, two.

I am trying:

gsed '/[a-z]/!c\L&'

It's matching the lines correctly, but replacing them with: L&

One, Two.
OnE, Two.
L&

How do I get it convert to lower case?

I tried 2 or 3 backslashes before the L but it just puts them in the output.

I will use awk or tr or some other utility if there's a better choice.

Also, if the version matters:

gsed --version
gsed (GNU sed) 4.9
1
  • Do you need to handle ÜNICØDÉ ? (thx @Philippos for this text!). Apr 29 at 18:50

8 Answers 8

18

Using any POSIX awk:

$ awk '!/[[:lower:]]/ { $0=tolower($0) } 1' file
One, Two.
OnE, Two.
one, two.
8

I think you need to use s/// for & to work, e.g.:

$ sed -e '/[a-z]/!s/.*/\L&/' < test.txt
One, Two.
OnE, Two.
one, two.

You could do the same or similar with Perl (which might be available where GNU sed isn't):

% perl -pe 's/.*/\L$&/ if not /[[:lower:]]/' < test.txt
One, Two.
OnE, Two.
one, two.

or:

% perl -pe '$_ = lc if not /[[:lower:]]/' < test.txt
One, Two.
OnE, Two.
one, two.
2
  • For standard sed, the y command is the tool of choice. Unfortunally, it doesn't accept character collections like [A-Z]. Thus, you need some support by the shell: sed "/[a-z]/! y/$(printf %s {A..Z} / {a..z})/"
    – Philippos
    Apr 24 at 10:18
  • 1
    @Philippos, and then you get to the issue that brace expansion isn't a standard feature, so you'd need to just spell out the list ;)
    – ilkkachu
    Apr 24 at 10:19
5

With Perl's ternary operator:

$ perl -ne 'print /[a-z]/ ? $_ : lc' file
One, Two.
OnE, Two.
one, two.

Explanations

Operator Meaning
perl executable
-n parse current file content as if we use while (<>) ...
-e Perl's expression
print print
/regex/ ? : ternary operator, based on the matching of boolean result of the regex
/[a-z]/ if there's a lower case matching...
$_ ...then print current default variable of current line $_
lc ...else print the current line in lower case (implicit use of $_)
2

I would do this in perl:

$ perl -pe '$_ = lc() if /^[A-Z., ]+$/' file 
One, Two.
OnE, Two.
one, two.

The options are -e to pass a script, and -p to print every line of the input after applying the script given by -e to it. The script itself uses the special variable $_ which means "the current line" and it converts it to lowercase (lc prints its input string in lower case, and by default, it acts on $_) only if the current line contains nothing but capital letters, spaces, commas and full stops (/^[A-Z., ]+$/).


If you go for a perl approach, use Ilkkachu's instead of mine since his doesn't depend on defining acceptable characters, and instead works by looking for lower case ones specifically.

0

Here's my (short and sweet) Perl solution:

perl -wpe '$_ = lc unless m/[a-z]/' file.txt

High-level explanation: This is simply saying: Before printing out each line in file file.txt, convert the line to lowercase unless it contains any lower-case letters.

You can also replace m/[a-z]/ with tr/a-z// so that it looks like this:

perl -wpe '$_ = lc unless tr/a-z//' file.txt

The difference between them is that m/[a-z]/ is meant to be evaluated as a boolean (that is, whether or not it contains the characters a thru z), whereas tr/a-z// returns the number of characters in the range of a thru z. (If it returns a number greater than zero, then the lower-casing (that is, $_ = lc) will not happen.)

Which one is better? I haven't benchmarked them, but the difference is probably so small that it's better to just use whichever one you're more comfortable with.

But remember: The m/[a-z]/ approach uses the [ and ] square brackets, whereas the tr/a-z// approach does not, but ends with two /s in a row. It may be a bit much to remember both, so I recommend using whichever one is easier for you to remember.

0

Using Raku (formerly known as Perl_6)

~$ raku -ne 'if / <:Ll> / { $_.put } else { $_.lc.put };'  file

#OR (more simply)

~$ raku -ne 'put / <:Ll> / ?? $_ !! $_.lc;'  file

The first two answers below use Raku's -ne non-autoprinting command line switches. The regex character class <:Ll> stands for "Unicode Letter lower". The first answer above is obviously an if/else construct. The second answer uses Raku's ternary operator ( < test > ?? True !! False ).

MORE SOLUTIONS:

~$ raku -e 'for lines() { when / <:Ll> / {.put}; default {$_.lc.put} };'  file 

#OR

~$ raku -pe '$_ .= lc if not / <:Ll> /;'  file

The third answer (first line in code block immediately above) uses Raku's "case" statement construction: when and default are used to satisfy the substitution criteria.

The fourth answer (second line in code block immediately above) uses Raku's -pe autoprinting (sed-like) command line flags, in conjunction with Raku's .= mutating-assignment operator.


Sample Input:

One, Two.
OnE, Two.
ONE, TWO.

ÜNICØDÉ
РУССКИЙ ЯЗЫК RUSSIAN
ΝΈΑ ΕΛΛΗΝΙΚΆ GREEK
"HEISS HEIẞ" GERMAN (WORD)

Sample Output (all code examples):

One, Two.
OnE, Two.
one, two.

ünicødé
русский язык russian
νέα ελληνικά greek
"heiss heiß" german (word)

The Raku answers handle Unicode (tested briefly above). For example, in the last line note the uppercase German (U+1E9E) is successfully converted to lowercase German ß (U+00DF). (This lettercasing was adopted into Unicode in 2008 and according to Wikipedia, "The capital letter was finally adopted as an option in standard German orthography in 2017.").

https://raku.org
https://docs.raku.org/language/operators#infix_??_!!
https://docs.raku.org/language/control#when https://en.wikipedia.org/wiki/ß#Development_of_a_capital_form

0

You can do it in Python as follows:

Create a Python script

 % cat all_upper_to_lower.py 
import sys

data = sys.stdin.readlines()
for line in data:
    line = line.rstrip('\n')
    new_line = line.lower() if line.isupper() else line
    print(new_line)

Create some sample data

 % cat input.txt 
One, Two.
OnE, Two.
ONE, TWO.

Run the script

 % cat input.txt | python all_upper_to_lower.py 
One, Two.
OnE, Two.
one, two.
0

Here is a ruby:

ruby -lpe ' $_=$_.downcase unless /[[:lower:]]/' file 

Prints:

One, Two.
OnE, Two.
one, two.

How this works:

  1. Command line switches -lpe do the following:

    -l chomp the line ending;

    -p Put a while gets(); ... end for sed like processing;

    -e Execute the script in the string following

  2. $_=$_.downcase make the line of input lowercase before printing

  3. unless /[[:lower:]]/ If there is any lowercase, do not call downcase. You could also negate and do if !/[[:lower:]]/

And you can do something similar in Perl:

perl -lpE '$_=lc $_ unless /[[:lower:]]/' file 
# same

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .