Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a text file, each line is stored like this :

"Video or movie"    "parent"    "Media or entertainment"    "1" "1" "1" "0" "0"

I want to swap the columns 3 with 2, i.e.

"Video or movie"   "Media or entertainment"  "parent"   "1" "1" "1" "0" "0"

How to do it in linux in a shell script or any other scripting language? I just need a simplest and quickest way of doing it.

share|improve this question
Your last sentence is incomplete. – Faheem Mitha Feb 14 '12 at 3:18
You could give R a try. – Faheem Mitha Feb 14 '12 at 3:41
A CSV processor, with whitespace as the column separator? – Gilles Feb 14 '12 at 7:41
@FaheemMitha Can you gives us an example here how you would do it with R? – Masi Jul 1 '15 at 14:36
up vote 8 down vote accepted

It can be done with awk using " as the field separator. But doing that, you must remember that $1 is empty, $2 holds the first string, $3 is the space between strings, $4 is the second string, etc. Also, it's more reliable to swap the two strings instead of just printing all the fields and hoping you put enough $ns. Bearing these in mind, the following should work:

awk 'BEGIN{OFS=FS="\""} {tmp=$4;$4=$6;$6=tmp;print}' input_file >output_file
share|improve this answer
+1 for such an elegant use of awk. – Stephen Quan Feb 14 '12 at 3:23

Here goes a quick and dirty sed that does it:

sed -e 's/^\("[^"]*"\) *\("[^"]*"\) *\("[^"]*"\)/\1 \3 \2/'

But will fail for fields with double quotes in their values, etc.

An example:

echo \"a\" \"b\" \"c d d d\" \"e\" | sed -e 's/^\("[^"]*"\) *\("[^"]*"\)  *\("[^"]*"\)/\1 \3 \2/'

But I'm pretty sure someone will be able to show you an awk one-liner that is simpler and better.

share|improve this answer
it did not work for this line: "bored of the main" "melody" "main plot" "1" "1" "1" "0" "1.0" "0" "0" – user11498 Feb 13 '12 at 20:49
@frankmoss I get '"bored of the main" "main plot" "melody" "1"', is that not what you want? What are you getting? – Kevin Feb 13 '12 at 21:02
It might be a white-space issue, try changing ` *` to [ \t]*. – Kevin Feb 13 '12 at 21:03
@Kevin i get the original line. I replaced with [ \t]* but no change in the result. How does your sed look? – user11498 Feb 13 '12 at 21:16
@frankmoss I put in echo '"bored of the main" "melody" "main plot" "1"' | sed -e 's/^\("[^"]*"\) *\("[^"]*"\) *\("[^"]*"\)/\1 \3 \2/', and got out "bored of the main" "main plot" "melody" "1" - copied and pasted, no typos in either. GNU sed version 4.2.1 – Kevin Feb 13 '12 at 21:23

I'd go with:

sed 's/"\(.*\)"/\1/' |
    awk 'BEGIN{FS="\" +\"";OFS="\" \""}{t=$3;$3=$2;$2=t;print}' |
    sed 's/.*/"&"/'

The two sed scripts handle the leading and trailing double quotes (since they are not delimitors and interfere). The BEGIN clause handles the separation of fields. The t=$3;$3=$2;$2=t is a standard swap fields idiom, then the whole field is printed (with OFS as the field separator).

share|improve this answer
it does not work. I send a single line file, and it prints the same file. – user11498 Feb 13 '12 at 21:49
Using your input ("Video or movie" "parent" "Media or entertainment" "1" "1" "1" "0" "0") and copying the above (not using my previous incarnations), I get "Video or movie" "Media or entertainment" "parent" "1" "1" "1" "0" "0". – Arcege Feb 13 '12 at 22:26

This method is effectively identical to Kevin's awk method.. I've include it here just as a comparison between bash and awk.

IFS=\";   # IFS sets up the split-at array delimiter
cat file |
while IFS= read -r line ;do              # Disable IFS for each `read' 
    A=($line)                            # split into array elements
    t="${A[5]}";A[5]="${A[3]}";A[3]="$t" # swap "columns" 2 and 3
    for ((i=1;i<$((${#A[@]}));i++)) ;do
       printf '"%s' "${A[$i]}"           # print each element with a lead "
    done; echo '"'                       # add the final "
share|improve this answer

What about something like this:

awk '{print $1, $3, $2, $4, $5, $6, $7, $8}' file > newfile
share|improve this answer
@ramonovsky awk doesn't like combination of quotes and space... i.e. it does not work – user11498 Feb 13 '12 at 21:25
You can specify ' ' as -F (field separator) – ramonovski Feb 13 '12 at 21:28
And what does -F' ' do? The problem is that this will print (e.g. in the first case) "Video movie" or "parent" ...`, quite clearly not what frank wants. – Kevin Feb 13 '12 at 22:32

I have two solutions.

ANSWER ONE: Use sed 3 times to do the following:

  1. start with the original line: 12345678
  2. duplicate the first two parameters: 1212345678
  3. chop the 4th parameter: 121345678
  4. chop the 1st parameter: 21345678

Here's the resultant command using sed (Using XXX and YYY as helpers to find and remove the 1st and 4th parameters):

sed 's/["][^"]*["][^"]*["][^"]*["]/XXX& & YYY/' data.txt | sed 's/["][^"]*["] YYY//' | sed 's/XXX["][^"]*["]//'

ANSWER TWO: Convert data into a script and run it!

  • convert the data.txt into a data.sh by inserting a command (say flipcol.sh)
  • run the data.sh

Implement flipcol.sh as:

echo '"'$2'"' '"'$1'"' '"'$3'"' '"'$4'"' '"'$5'"' '"'$6'"' '"'$7'"' '"'$8'"'

Then run the following command on your data.txt creating a shell script:

sed 's!^!./flipcol.sh !' < data.txt > data.sh

Then run the newly created shell script

share|improve this answer

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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