How to print lines where it matchs specific number in bash?

Viewed 143

I have a text file called file.txt like below,

01_ABC_0000  AA
02_CDE_0000  BB
03_EFG_0000  CC
04_ABC_0001  DD
05_CDE_0001  EE
06_EFG_0001  FF

where it should separated into two different files, like file0.txt

01_ABC_0000  AA
02_CDE_0000  BB
03_EFG_0000  CC

and file1.txt

04_ABC_0001  DD
05_CDE_0001  EE
06_EFG_0001  FF

what i have been trying, cat file00.txt | awk '{print $1}' | sed 's/.*\(....\)/\1/') to get the only numbers from first word but I am not able use this to go forward separating it into the two files.

Any help is much appreciated.

EDIT: Sorry, I have edited the question where the first field is 01_ABC_0000 something like this, If I use field separator as underscore it doesn't work as expected.

7 Answers

1st solution: Considering your entries are sorted with values of 2nd column(0000, 00001 and so on). With your shown samples, please try following awk program.

awk -v count="1" -F'_| +' '
prev!=$2{
  count++
  close(outputFile)
  outputFile=("file"count".txt")
  prev=$2
}
{
  print > (outputFile)
}
'  Input_file


2nd solution: Using sort + awk combination solution in case entries are not sorted.

awk -F'_| +' '{print $2,$0}' Input_file | 
sort -nk1                               | 
awk -v count="1" -F'_| +' '
{
  sub(/^[^[:space:]]+[[:space:]]+/,"")
}
prev!=$2{
  count++
  close(outputFile)
  outputFile=("file"count".txt")
  prev=$2
}
{
  print > (outputFile)
}
'

If you only need to do this for 2 numbers, you can use

cat file00.txt | grep "_0000" >> file1.txt
cat file00.txt | grep "_0001" >> file2.txt

The typical way to do this with awk is something like:

awk '{outfile = sprintf("file%d.txt", $2 + 1); print > outfile}' FS='[_ ]' input

The manner in which you parse out the relevant number to use will change with the input format. Also, as the input file grows larger you may to worry about running out of resources, so you might want to close the files explictly with something like:

awk '{outfile = sprintf("file%d.txt", $2 + 1); print >> outfile; close(outfile)}' FS='[_ ]' input

Which requires you to add some additional logic to ensure that the files are empty or do not exist before you begin.

awk -F'[_ ]' '{print $0 > "file"substr($2,4,1)".txt"}' file.txt 
total 4
-rw-r--r--  1 501  20  96 Jun 14 00:49 inputfile.txt

|

 # 2nd FS choice if you're VERY certain the format is fixed
 
 {m,g}awk '{ print >("file"$(NF-!_)".txt") }' FS='^[^_]+_[^_]+_|[ \t]+[^ \t]+$'
                                              FS=' .+$|.+_'

total 12
-rw-r--r--  1 501  20  48 Jun 14 00:52 file0000.txt
-rw-r--r--  1 501  20  48 Jun 14 00:52 file0001.txt
-rw-r--r--  1 501  20  96 Jun 14 00:49 inputfile.txt
 

==> file0000.txt <==
01_ABC_0000  AA
02_CDE_0000  BB
03_EFG_0000  CC

==> file0001.txt <==
04_ABC_0001  DD
05_CDE_0001  EE
06_EFG_0001  FF

This might work for you (GNU uniq and csplit):

uniq -s4 -w4 --group file | csplit --supp -f file -b '%02d.txt' - '/^$/' '{*}'

Use uniq to separate each group of lines by an empty line, where groups are determined by skipping the first 4 characters and matching on the following 4.

Pass the output from the above into csplit.

Suppress the matching lines (blank lines) and split the groups into files named file and suffix of %02d.txt where %02 is replaced by 00, 01 ....

Alternative using cat and sed:

cat -n file | 
sed -En 'H;1h;$!d;x
         s/^\s+(\S+).*_(\S+).*/\1 \2/mg
         s/(\S+) (\S+)\n.*(\S+) \2\n?/\1,\3w file\2.txt\n/gp' | 
sed -nf - file

Creates a sed script with the ranges and file number from the original data and then runs the script against the data.

Using sed

num=$(sort -t'_' -k2 file00.txt | sed -n '$s/[^_]*_\([^ ]*\).*/\1/p')
for ((i=0;i<=num;i++)); do
    sed -n "/0\+$i\b/p" file00.txt >> file$i.txt
done

Which produces the following output

$ head file*
==> file0.txt <==
ABC_0000  AA
CDE_0000  BB
EFG_0000  CC

==> file1.txt <==
ABC_0001  DD
CDE_0001  EE
EFG_0001  FF
Related