-2

I have a file named swap:

$ cat swap
aardvark BILL CLINTON dog
eternal CLINTON BILL forever
BILL good housekeeping BILL

I want to change all occurrences of BILL to CLINTON and concurrently change all occurrences of CLINTON to BILL, resulting in output as:

aardvark CLINTON BILL dog
eternal BILL CLINTON forever
CLINTON good housekeeping CLINTON

I know the classic solution:

sed -e 's/BILL/temp/g' -e 's/CLINTON/BILL/g' -e 's/temp/CLINTON/g'

but somebody challenged me, as an exercise, to do it without using a third word.

I was using following command, but it was working only on the first line:

$ sed 's/\(BILL\) \(CLINTON\)/\2 \1/g' swap
aardvark CLINTON BILL dog
eternal CLINTON BILL forever
BILL good housekeeping BILL

Note: I am using Solaris 10 OS.

How could I get the desired output while observing this artificial constraint?

Behavior for

MORBILLIFORM OVERBILLED
TOM, BILL, AND HARRISON

is unspecified.

6
  • 1
    By what logic is "bill bill" transforming to "clinton clinton" on the last line?
    – user218374
    Jun 25 '17 at 12:48
  • 1
    sed 's/\(.*\) \(.*\)/\2 \1/' swap
    – user218374
    Jun 25 '17 at 12:50
  • Why such an artificial constraint?
    – roaima
    Jun 25 '17 at 13:18
  • @roaima Someone had thrown me a challenge to do this without using 3rd variable Jun 25 '17 at 16:41
  • @RakeshSharma, I guess swap is not the correct word for this scenario but I believe you understood what I want, let me know if you could help me with the whole scenario. Jun 25 '17 at 16:47
3
perl -pe 's/(BILL)|CLINTON/$1 ? "CLINTON" : "BILL"/eg' swap

Explanation:

-p option makes Perl read the file a la awk, line-by-line AND current record is auto-printed after all transformations have been applied.

The s/// command works on the current line. The /eg pattern modifiers are: /g will apply the transformation globally on the current line and not just limit itself to the first had you not mentioned the /g. The /e is the eval-uate modifier, which makes the RHS of the s/// to be taken as a Perl code and after it's evaluation what value it results in is taken as as the RHS.

So, s/(BILL)|CLINTON/.../ will look for BILL or CLINTON in the current record starting the scanning from the left. When BILL is found, then $1 is set and hence the Perl expression $1 ? "CLINTON" : "BILL" will evaluate to CLINTON which is what BILL is replaced with in the current record. But we"re done yet, due to the /g modifier. Similarly, if CLINTON is found, then $1 is empty and so the Perl expression $1 ? "CLINTON" : "BILL" is evaluated to BILL and which is what CLINTON is replaced with in the record. This goes on till the end of current reached , at which point it is printed to STDOUT due to the -p option.

sed -e 's/BILL\|CLINTON/\n&/g;s/\nBILL/CLINTON/g;s/\nCLINTON/BILL/g' swap
4
  • Thanks!! Perl command is working fine. Could you please explain what does "e" do in "eg" Jul 26 '17 at 13:47
  • In fact, Could you please explain whole perl command you have used here. Jul 26 '17 at 13:52
  • Thanks!! 1 more question, you have placed BILL in brackets "(BILL)" but CLINTON without brackets in s/(BILL)|CLINTON/,reason? Jul 26 '17 at 15:08
  • @Raid_Master Since there are only 2 possibilities, viz., we either match BILL or CLINTON, so that means if we don't match BILL => we have CLINTON.
    – user218374
    Jul 26 '17 at 15:34
2

awk solution:

awk '{ for(i=1;i<=NF;i++) { if($i=="BILL") $i="CLINTON"; else if($i=="CLINTON") $i="BILL" } }1'  swap

The output:

CLINTON BILL
BILL CLINTON
CLINTON CLINTON

  • for(i=1;i<=NF;i++) - iterating through all fields (awk treats whitespace(s) as default field separator)

  • if($i=="BILL") $i="CLINTON" - if a field value is equal to BILL - assign it with CLINTON

  • else if($i=="CLINTON") $i="BILL" - else if a field value is equal to CLINTON - assign it with BILL

11
  • awk '{ for(i=1;i<=NF;i++) { if($i=="BILL") $i="CLINTON"; else if($i=="CLINTON") $i="BILL" } }1' swap awk: syntax error near line 1 awk: bailing out near line 1 Jun 25 '17 at 11:53
  • @Raid_Master, that would not happen, except you are using non-Linux OS or different awk implementation. See this linuxquestions.org/questions/linux-newbie-8/… Jun 25 '17 at 11:56
  • I am using Solaris 10 OS Jun 25 '17 at 12:05
  • check your awk implementation Jun 25 '17 at 12:12
  • Could you please explain what this part your command is doing : for(i=1;i<=NF;i++)(you are initializing i as 1 and equating it with "BILL"?? ) Jun 25 '17 at 16:59
1

Another awk solution

awk '{
        while (match($0, /BILL|CLINTON/)) {
            printf "%s", substr($0, 1, RSTART-1);
            $0 = substr($0, RSTART);
            printf "%s", /^BILL/ ? "CLINTON" : "BILL";
            $0 = substr($0, 1+RLENGTH)
        }
        print
    }' swap

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.