Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A Linux Shell Script Problem

I have a string separated by dot in Linux Shell,

$example=This.is.My.String

I want to

1.Add some string before the last dot, for example, I want to add "Good.Long" before the last dot, so I get:

This.is.My.Goood.Long.String

2.Get the part after the last dot, so I will get

String

3.Turn the dot into underscore except the last dot, so I will get

This_is_My.String

If you have time, please explain a little bit, I am still learning Regular Expression.

Thanks a lot!

like image 663
DocWiki Avatar asked Nov 09 '10 20:11

DocWiki


People also ask

What is $? == 0 in shell script?

$? is the exit status of the most recently-executed command; by convention, 0 means success and anything else indicates failure. That line is testing whether the grep command succeeded. The grep manpage states: The exit status is 0 if selected lines are found, and 1 if not found.

What does $() mean bash?

Example of command substitution using $() in Linux: Again, $() is a command substitution which means that it “reassigns the output of a command or even multiple commands; it literally plugs the command output into another context” (Source).


3 Answers

I don't know what you mean by 'Linux Shell' so I will assume bash. This solution will also work in zsh, etcetera:

example=This.is.My.String
before_last_dot=${example%.*}
after_last_dot=${example##*.}
echo ${before_last_dot}.Goood.Long.${after_last_dot} 
This.is.My.Goood.Long.String

echo ${before_last_dot//./_}.${after_last_dot} 
This_is_My.String

The interim variables before_last_dot and after_last_dot should explain my usage of the % and ## operators. The //, I also think is self-explanatory but I'd be happy to clarify if you have any questions.

This doesn't use sed (or even regular expressions), but bash's inbuilt parameter substitution. I prefer to stick to just one language per script, with as few forks as possible :-)

like image 196
Johnsyweb Avatar answered Sep 23 '22 23:09

Johnsyweb


Other users have given good answers for #1 and #2. There are some disadvantages to some of the answers for #3. In one case, you have to run the substitution twice. In another, if your string has other underscores they might get clobbered. This command works in one go and only affects dots:

sed 's/\(.*\)\./\1\n./;h;s/[^\n]*\n//;x;s/\n.*//;s/\./_/g;G;s/\n//'
  1. It splits the line before the last dot by inserting a newline and copies the result into hold space:

    s/\(.*\)\./\1\n./;h
    
  2. removes everything up to and including the newline from the copy in pattern space and swaps hold space and pattern space:

    s/[^\n]*\n//;x
    
  3. removes everything after and including the newline from the copy that's now in pattern space

    s/\n.*//
    
  4. changes all dots into underscores in the copy in pattern space and appends hold space onto the end of pattern space

    s/\./_/g;G
    
  5. removes the newline that the append operation adds

    s/\n//
    

Then the sed script is finished and the pattern space is output.

At the end of each numbered step (some consist of two actual steps):

Step        Pattern Space                 Hold Space

  1.        This.is.My\n.String       This.is.My\n.String

  2.        This.is.My\n.String       .String

  3.        This.is.My                        .String

  4.        This_is_My\n.String     .String

  5.        This_is_My.String            .String

like image 27
Dennis Williamson Avatar answered Sep 24 '22 23:09

Dennis Williamson


Solution

  1. Two versions of this, too:
    • Complex: sed 's/\(.*\)\([.][^.]*$\)/\1.Goood.Long\2/'
    • Simple: sed 's/.*\./&Goood.Long./' - thanks Dennis Williamson
  2. What do you want?
    • Complex: sed 's/.*[.]\([^.]*\)$/\1/'
    • Simpler: sed 's/.*\.//' - thanks, glenn jackman.
  3. sed 's/\([^.]*\)[.]\([^.]*[.]\)/\1_\2/g'

With 3, you probably need to run the substitute (in its entirety) at least twice, in general.

Explanation

Remember, in sed, the notation \(...\) is a 'capture' that can be referenced as '\1' or similar in the replacement text.

  1. Capture everything up to a string starting with a dot followed by a sequence of non-dots (which you also capture); replace by what came before the last dot, the new material, and the last dot and what came after it.

  2. Ignore everything up to the last dot followed by a capture of a sequence of non-dots; replace with the capture only.

  3. Find and capture a sequence of non-dots, a dot (not captured), followed by a sequence of non-dots and a dot; replace the first dot with an underscore. This is done globally, but the second and subsequent matches won't touch anything already matched. Therefore, I think you need ceil(log2N) passes, where N is the number of dots to be replaced. One pass deals with 1 dot to replace; two passes deals with 2 or 3; three passes deals with 4-7, and so on.

like image 44
Jonathan Leffler Avatar answered Sep 23 '22 23:09

Jonathan Leffler