25

I want to delete the last column of a txt file, while I do not know what the column number is. How could I do this?

Example:

Input:

1223 1234 1323 ... 2222 123
1233 1234 1233 ... 3444 125
0000 5553 3455 ... 2334 222

And I want my output to be:

1223 1234 1323 ... 2222
1233 1234 1233 ... 3444
0000 5553 3455 ... 2334
  • There are many ways to do this..please add an example and your expected output from it.. – heemayl Oct 7 '15 at 6:07
  • @heemayl ok I did – zara Oct 7 '15 at 6:11
  • Thanks..are the columns tab separated or space separated? – heemayl Oct 7 '15 at 6:12
  • @heemayl space is deliminator – zara Oct 7 '15 at 6:14
43

With awk:

awk 'NF{NF-=1};1' <in >out

or:

awk 'NF{NF--};1' <in >out

or:

awk 'NF{--NF};1' <in >out

Although this looks like voodoo, it works. There are three parts to each of these awk commands.

The first is NF, which is a precondition for the second part. NF is a variable containing the number of fields in a line. In AWK, things are true if they're not 0 or empty string "". Hence, the second part (where NF is decremented) only happens if NF is not 0.

The second part (either NF-=1 NF-- or --NF) is just subtracting one from the NF variable. This prevent the last field from being printed, because when you change a field (removing the last field in this case), awk re-construct $0, concatenate all fields separated by space by default. $0 didn't contain the last field anymore.

The final part is 1. It's not magical, it's just used as a expression that means true. If an awk expression evaluates to true without any associated action, awk default action is print $0.

  • @JJoao: Ah, thanks, forgot about --. A note , currently, you need ;1 for POSIX compliant. – cuonglm Oct 7 '15 at 10:04
  • My initial instinct would be to use a for loop, but this is much more concise and clever. – Sergiy Kolodyazhnyy Oct 8 '15 at 13:22
  • 5
    It's worth noting that if you're using a non-default delimiter, you'll need to make some changes. Assuming , is your delimiter: awk -F',' 'BEGIN { OFS = FS }; NF { NF -= 1 }; 1' < in > out – Mr. Llama Mar 28 '18 at 16:04
  • 1
    The effect of decrementing NF is undefined behavior by POSIX - you will get different output depending on which awk you're running. Some awks will remove the last field as you want, some will do nothing at all, and others could report a syntax error or so anything else. – Ed Morton Oct 29 '18 at 11:38
16

Using grep with PCRE:

$ grep -Po '.*(?=\s+[^\s]+$)' file.txt 
1223 1234 1323 ... 2222
1233 1234 1233 ... 3444
0000 5553 3455 ... 2334

Using GNU sed:

$ sed -r 's/(.*)\s+[^\s]+$/\1/' file.txt 
1223 1234 1323 ... 2222
1233 1234 1233 ... 3444
0000 5553 3455 ... 2334
  • 1
    @ramin Sure..could you please ask it as a new question (this is how this site works) :) – heemayl Oct 7 '15 at 6:26
  • @ramin Does it give you any time restriction or any warning? – heemayl Oct 7 '15 at 6:29
  • it says this is out of standard question! – zara Oct 7 '15 at 6:29
  • @ramin Ok..let me contact an admin, may be they can help you with it..btw did you check any old QA regarding your question? its a possibility that the question is already asked and answered.. – heemayl Oct 7 '15 at 6:31
  • 3
    Don't ask super basic questions like "how can i rename a file name in Linux". Use Google. – Christoffer Hammarström Oct 7 '15 at 14:46
11

Using Perl:

perl -lane '$,=" ";pop(@F);print(@F)' in

Using rev + cut:

rev in | cut -d ' ' -f 2- | rev
5

Using GNU sed:

sed -r 's/\s+\S+$//' input.txt

More generally, this one works with the BSD sed in OSX, as well as GNU sed:

sed 's/[[:space:]]\{1,\}[^[:space:]]\{1,\}$//' input.txt
1

If the delimiter is always a single char (so two or more consecutive delimiters designate empty fields), you could head just the first line from your input file, count the delimiters (n delimiters means number of fields is n+1) then use cut to print from the 1st field up to the nth field (second to last one), e.g. with tab-delimited input:

n=$(head -n 1 infile | tr -dc \\t | tr \\t \\n | wc -l)
cut -f1-$n infile > outfile

or e.g. with a csv file:

n=$(head -n 1 infile | tr -dc , | tr , \\n | wc -l)
cut -d, -f1-$n infile > outfile

I'll run some benchmarks later if I have the time but with huge input I think this solution should be faster than other solutions that use regex as this one does minimal processing on the first line to get the no. of fields and then uses cut which is optimized for this job.

1

Portably you can use either of these:

sed 's/[[:space:]]*[^[:space:]]*$//' file

awk '{sub(/[[:space:]]*[^[:space:]]*$/,"")}1' file
0

Using vim:

Open file in vim

vim <filename> 

Go to first row, just in case the cursor is placed anywhere else.

gg

Create a macro named "q" qq, that goes to the back of the current line $, then goes back to the last space F (capital F, followed by literal SPACE) then delete from current position through end of line D go down to the next line j and stop macro recording with q.

qq$F Djq

Now we can repeat our macro with @q for each line.
We can also press @@ to repeat last macro or even easier:

99@q

to repeat the macro 99 times.
Note: The number must not exactly match the lines.

0

For people who have a similar problem but with different field separators this awk method will preserve the field separator correctly:

$ cat file 
foo.bar.baz
baz.bar.foo
$ awk -F'.' 'sub(FS $NF,x)' file
foo.bar
baz.bar

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.