如何在for循环中的awk中指定一行?

时间:2013-08-26 18:48:05

标签: bash parsing awk

我正在使用以下awk命令:

my_command | awk -F "[[:space:]]{2,}+" 'NR>1 {print $2}' | egrep "^[[:alnum:]]"

成功返回我的数据:

fileName1
file Name 1
file Nameone
f i l e Name 1

因为你可以看到一些文件名有空格。这很好,因为我只想回复文件名(没什么特别的)。问题是在循环中调用该特定行。我试着这样做:

i=1
for num in $rows
do
  fileName=$(my_command | awk -F "[[:space:]]{2,}+" 'NR==$i {print $2}' | egrep "^[[:alnum:]])"
  echo "$num $fileName"
  $((i++))
done

但我的输出总是null

我还尝试使用awk -v record=$i然后打印$record,但我得到了以下结果。

f i l e Name 1

修改

对于混淆感到抱歉:rows是一个变量,列出了像11 12 13这样的ID 这些ID中的每一个都与文件名相关联。我没有进行任何解析的命令如下所示:

     id      File Info      OS
     11      File Name1     OS1
     12      Fi leNa me2    OS2
     13      FileName 3     OS3

我只能使用id字段来运行我需要的命令,但是我想使用File Info字段来通知用户该命令的实际File正在被执行。

4 个答案:

答案 0 :(得分:2)

我认为您的$i未按预期扩展。你应该用这种方式引用你的论点:

  fileName=$(my_command | awk -F "[[:space:]]{2,}+" "NR==$i {print \$2}" | egrep "^[[:alnum:]]")

你忘记了另一个)

修改

作为对您的要求的更新,您可以将行传递给单个awk命令而不是循环内的重复命令:

#!/bin/bash

ROWS=(11 12)

function my_command {
    # This function just emulates my_command and should be removed later.
    echo "     id      File Info      OS
     11      File Name1     OS1
     12      Fi leNa me2    OS2
     13      FileName 3     OS3"
}

awk -- '
    BEGIN {
        input = ARGV[1]
        while (getline line < input) {
            sub(/^ +/, "", line)
            split(line, a, /   +/)
            for (i = 2; i < ARGC; ++i) {
                if (a[1] == ARGV[i]) {
                    printf "%s %s\n", a[1], a[2]
                    break
                }
            }
        }
        exit
    }
' <(my_command) "${ROWS[@]}"

awk命令可以压缩为一行:

awk -- 'BEGIN { input = ARGV[1]; while (getline line < input) { sub(/^ +/, "", line); split(line, a, /   +/); for (i = 2; i < ARGC; ++i) { if (a[1] == ARGV[i]) {; printf "%s %s\n", a[1], a[2]; break; }; }; }; exit; }' <(my_command) "${ROWS[@]}"

或者更好的是只使用Bash作为一个整体:

#!/bin/bash

ROWS=(11 12)

while IFS=$' ' read -r LINE; do
    IFS='|' read -ra FIELDS <<< "${LINE//  +( )/|}"
    for R in "${ROWS[@]}"; do
        if [[ ${FIELDS[0]} == "$R" ]]; then
            echo "${R} ${FIELDS[1]}"
            break
        fi
    done
done < <(my_command)

它应该提供如下输出:

11 File Name1
12 Fi leNa me2

答案 1 :(得分:2)

Shell变量不会在单引号字符串中展开。使用-v选项将awk变量设置为shell变量:

fileName=$(my_command | awk -v i=$i -F "[[:space:]]{2,}+" 'NR==i {print $2}' | egrep "^[[:alnum:]])"

此方法避免了必须转义$脚本中的所有awk字符,这是konsolebox答案中的要求。

答案 2 :(得分:1)

每次通过循环重新运行my_command(和awk)只是为了从输出中提取一行是非常低效的。特别是当您所做的只是按顺序打印出每行的一部分时。 (我假设my_command确实是完全相同的命令,并且每次循环都会产生相同的输出。)

如果是这种情况,这个单行应该可以解决问题:

paste -d' ' <(printf '%s\n' $rows) <(my_command | 
  awk -F '[[:space:]]{2,}+' '($2 ~ /^[::alnum::]/) {print $2}')

答案 3 :(得分:1)

正如您已经听说过的,您需要从shell变量填充awk变量,以便能够在awk脚本中使用所需的值,因此:

awk -F "[[:space:]]{2,}+" 'NR==$i {print $2}' | egrep "^[[:alnum:]]"

应该是这样的:

awk -v i="$i" -F "[[:space:]]{2,}+" 'NR==i {print $2}' | egrep "^[[:alnum:]]"

此外,你不需要awk和grep,因为awk可以做grep van所做的任何事情,所以你可以改变你脚本的这一部分:

awk -v i="$i" -F "[[:space:]]{2,}+" 'NR==i {print $2}' | egrep "^[[:alnum:]]"

到此:

awk -v i="$i" -F "[[:space:]]{2,}+" '(NR==i) && ($2~/^[[:alnum:]]/){print $2}'

并且您在数字范围后不需要+,因此您可以将{2,}+更改为{2,}

awk -v i="$i" -F "[[:space:]]{2,}" '(NR==i) && ($2~/^[[:alnum:]]/){print $2}'

最重要的是,不是每次调用my_command都调用一次awk,而是可以为它们调用一次,而不是这个(假设这样做你想要的):

i=1
for num in rows
do
  fileName=$(my_command | awk -v i="$i" -F "[[:space:]]{2,}" '(NR==i) && ($2~/^[[:alnum:]]/){print $2}')
  echo "$num $fileName"
  $((i++))
done

你可以做更多这样的事情:

for num in rows
do
  my_command
done |
awk -F '[[:space:]]{2,}' '$2~/^[[:alnum:]]/{print NR, $2}'

我说“类似”,因为你没有告诉我们“my_command”,“rows”或“num”是什么,所以我不能准确但希望你能看到这种模式。如果您向我们提供更多信息,我们可以提供更好的答案。