linux 使awk输出可变列数的数据

hgncfbus  于 2023-10-16  发布在  Linux
关注(0)|答案(7)|浏览(143)

新的 awk 用户在这里。我是一个模拟器的用户,它输出一个单独的ascurry文件,该文件是M行乘N列,空格分隔,每行末尾有换行符。行的数量不重要,但它是在数千。列的数量是数百。我想把这个大文件分成多个小文件,比如说,50列数据。
下面使用 awk 将可靠地使用空格作为分隔符来拆分文件,并打印前3列数据:

awk ‘{split($0,a)} {print a[1] “ “ a[2] " " a[3]}’ huge_file.txt > small_file.txt

有没有什么简单的方法可以让它打印出50列数据,而不需要很长的print语句?理想情况下,我希望它打印出从X到X+49的列。
到目前为止,我不得不使用split函数来获取 awk,以便以我认为可预测的方式分解文件。当我尝试使用类似于:

awk '{print $1 " " $2 " " $3}' huge_file.txt > small_file.txt

它从来没有正确的工作,即使我使用FS = " "
如果你能帮忙的话,我将不胜感激。

bt1cpqcv

bt1cpqcv1#

也许这个合适
制作一些“测试”数据(220列X 1000行):

awk 'BEGIN{for (i=1; i<=1000; i++) {for (j=1; j<=220; j++) {printf "%s%s", j, (j == 220 ? "\n" : " ")}}}' > test.txt

head -2 test.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

将'test.txt'拆分为较小的文件,每个文件包含50列:

awk '{for(i=1; i<=NF; i+=50){for(j=i; j<=i+49; j++){printf "%s%s", $j, (j == i + 49 ? "\n" : " ") > ("small_file" i ".txt")}; close ("small_file" i ".txt")}}' test.txt

head -2 small_file*
==> small_file1.txt <==
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

==> small_file51.txt <==
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

==> small_file101.txt <==
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

==> small_file151.txt <==
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200

==> small_file201.txt <==
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

更好的格式:

awk '{
    for (i = 1; i <= NF; i += 50) {
        for (j = i; j <= i + 49; j++) {
            printf("%s%s", $j, (j == i + 49 ? "\n" : " ")) > ("small_file" i ".txt")
        }
        close("small_file" i ".txt")
    }
}' test.txt
mkshixfv

mkshixfv2#

使用GNU awk,你可以使用FPAT来定义正则表达式的输入字段(在你的例子中是一组C=50个空格分隔的单词),并只打印第X组:

awk -v C=50 -v X=3 'BEGIN {FPAT="(\\S+\\s+){0," C-1 "}\\S+"}
  X<=NF {print $X}' huge_file.txt

这将打印第三(X=3)组50(C=50)列,即列101到150。当然,如果少于150列,则打印的列数将少于50列(如果少于101列,则打印的列数将少于50列)。
您也可以一次打印不同文件中的所有组:

awk -v C=50 'BEGIN {FPAT="(\\S+\\s+){0," C-1 "}\\S+"}
  {for(i=1;i<=NF;i++) print $i > "small_file." i ".txt"}' huge_file.txt

如果你有N=225列,这将创建5个名为small.1.txt,...,small.5.txt的文件。文件small.1.txt包含列1到50,small.2.txt包含列51到100,.,small.5.txt包含列201到225。
演示:

$ cat huge_file.txt 
A1 A2 A3 A4 A5
B1 B2 B3 B4 B5
C1 C2 C3 C4 C5

$ awk -v C=4 -v X=1 'BEGIN {FPAT="(\\S+\\s+){0," C-1 "}\\S+"}
  X<=NF {print $X}' huge_file.txt
A1 A2 A3 A4
B1 B2 B3 B4
C1 C2 C3 C4

$ awk -v C=4 -v X=2 'BEGIN {FPAT="(\\S+\\s+){0," C-1 "}\\S+"}
  X<=NF {print $X}' huge_file.txt
A5
B5
C5

$ awk -v C=4 'BEGIN {FPAT="(\\S+\\s+){0," C-1 "}\\S+"}
  {for(i=1;i<=NF;i++) print $i > "small_file." i ".txt"}' huge_file.txt

$ ls small_file.*.txt
small_file.1.txt  small_file.2.txt

$ cat small_file.1.txt 
A1 A2 A3 A4
B1 B2 B3 B4
C1 C2 C3 C4

$ cat small_file.2.txt 
A5
B5
C5
ve7v8dk2

ve7v8dk23#

coreutils可能更适合此任务(在bash中):

ncols=$(( $(head -n1 $infile | tr -dc ' ' | wc -c) + 1 ))
stepsz=50

# Compose column ranges
paste -d- <(seq 1 $stepsz $ncols) <(seq $stepsz $stepsz $ncols) |
while read rng; do
  cut -d' ' -f$rng $infile > outfile_$rng
done
qyswt5oh

qyswt5oh4#

它从来没有正确的工作,即使我使用FS = " "
这是GNU AWKFS的默认值,考虑到

awk 'END{print FS==" "}' emptyfile

给出输出
将内置变量值设置为默认值不会改变结果。
{print $1 " " $2 " " $3}
由于有字段分隔符(FS)用于输入,因此通常有字段分隔符(OFS)用于输出,当您使用逗号分隔print参数时使用,在这种情况下,它可能会重写为

{print $1,$2,$3}

它不仅更简洁,而且您可以通过设置OFS值来更改所有分隔符。OFS的默认值为单空格。如果您想了解更多关于FSOFS的信息,请阅读8 Powerful Awk Built-in Variables – FS, OFS, RS, ORS, NR, NF, FILENAME, FNR

  • (在GNU Awk 5.1.0中测试)*
clj7thdc

clj7thdc5#

下面是我对awk脚本的看法,我认为它可以满足您的需要。与Barmar的答案一样,它使用for循环迭代字段,但这个循环非常简单,awk新手也能理解。:)它将输出重定向到不同的文件,并且不需要GNU awk。

BEGIN {
  cols=50
}

{
  n=1
  for (i=1;i<=NF;i++) {
    output_file="file_" n
    printf("%s%s", $i, OFS) > output_file
    if (i%cols==0) n++
  }
  printf("%s", ORS) > output_file
}

你并不真的需要output_file变量,但我觉得它使事情更容易阅读。
因为有一个关于它的注解,你应该注意到这并没有显式地关闭文件。关闭文件的版本可能会将以下内容作为for循环的最后一行:

if (i%50==0) { n++; close(output_file) }

这会在我们处理完文件后关闭它们,但代价是开销和速度上的一点影响。如果你不需要,我就不麻烦了。

wwtsj6pe

wwtsj6pe6#

这个问题可能会导致awk必须创建更多的输出文件,而不是同时打开,所以这里有一个答案来避免这个问题,而不必在每次写入时打开和关闭输出文件,因为这会很慢。
在每个系统的基础上,一个进程一次可以打开多少个文件是有限制的,在某些系统上,ulimit -n会告诉你这个数字。如果你的操作系统支持,GNU awk可以让你通过内部管理按需打开/关闭文件,让你看起来有比一次打开的文件更多的文件,但是一旦进入这个阶段,它就会慢很多。其他awk会出现“打开的文件太多”错误(google awk too many open files)。下面是两种方法来避免这个问题。

**1)**每个输出文件打开一次输入文件,并一次一个地写入每个输出文件:

如果您的输入文件可能太大而无法容纳在内存中,那么您可以这样做:

awk -v FS='^$' -v numCols=50 '
    FNR == 1 {
        if ( NR == 1 ) {
            nf = split($0,tmp," ")
            numFiles = int((nf - 1) / numCols) + 1
            while ( ARGC <= (numFiles + 1) ) {
                ARGV[ARGC++] = FILENAME
            }
        }
        close(out)
        out = "out." (++fileNr)
    }
    { line = substr($0,prevLgth[FNR] + 1) }
    match(line,"([^[:space:]]+[[:space:]]+){0,"numCols-1"}[^[:space:]]+") {
        print substr(line,RSTART,RLENGTH) > out
        prevLgth[FNR] += RSTART + RLENGTH
    }
' file

这只是打开和关闭输入和输出文件一次,每个输出文件所需的和使用很少的内存。

**2)**首先将输入文件读入内存,然后一次写入一个完整的输出文件:

或者,如果你的输入确实适合内存,这也可以在任何POSIX awk中工作:

awk -v FS='^$' -v numCols=50 '
    { lines[NR] = $0 }
    END {
        nf = split(lines[1],tmp," ")
        numFiles = int((nf - 1) / numCols) + 1
        for (fileNr=1; fileNr<=numFiles; fileNr++ ) {
            out = "out." fileNr
            for ( lineNr=1; lineNr<=NR; lineNr++ ) {
                line = lines[lineNr]
                if ( match(line,"([^[:space:]]+[[:space:]]+){0,"numCols-1"}[^[:space:]]+") ) {
                    print substr(line,RSTART,RLENGTH) > out
                    lines[lineNr] = substr(line,RSTART+RLENGTH)
                }
            }
            close(out)
        }
    }
' file

并且会更有效一点,因为它不必重新打开输入文件并重新读取每个输出文件的内容。
我使用上面的-v FS='^$'nf=split($0,tmp," "),而不是将FS设置为" ",并使用NF以提高效率。我从nf=split($0,tmp," ")中使用nf而不是NF(并且只在第一个输入行读取时使用),因为有些awk,例如。GNU awk只会在脚本中提到特定字段(如$1NF)时进行字段拆分,否则他们知道您没有使用字段,因此他们不需要浪费时间将输入拆分为字段。以防万一你使用的awk会进行字段分割,我将FS设置为^$,这样它只会尝试匹配一个空行,而不是多次分割空格。

nwsw7zdq

nwsw7zdq7#

使用for循环遍历字段。外部循环以50为一组,并在每组后打印一个换行符。内部循环处理每个组,打印每个字段后面的空白。

awk '{for (start = 1; start <= NF; start += 50) {
        limit = start + 50 > NF ? NF : start + 50;
        for (cur = start; cur <= limit; cur++) {
            printf("%s ", $cur);
        }
        print;
    }' huge_file.txt > small_file.txt

相关问题