【问题标题】:Refining Bash loop精炼 Bash 循环
【发布时间】:2019-02-28 05:21:23
【问题描述】:

你好 Stackoverflow 社区,

请原谅我的天真,但我有一个基本的循环脚本运行一个更复杂的脚本,它正在寻找一个文本文件以获取我作为数组的输入。我猜我已经成功了,但我知道这可以运行得更好、更自动化。

这是我的脚本要查找的文本文件;

2014;14204;
2015;15042;
2015;15062;
...
end;

这是我作为循环运行的 bash 脚本;

{ while IFS=';' read  YEAR1 PROJ1 YEAR2 PROJ2 YEAR3 PROJ3 fake
    do 
        { echo "$YEAR1" | egrep '^#|^ *$' ; } > /dev/null && continue 
            $local/script.sh \
                --forinput1 $YEAR1/$PROJ1  \
                --forinput2 $YEAR2/$PROJ2  \
                --forinput3 $YEAR3/$PROJ3  \
    done 
} < textFile.txt

我自己进行了一些研究,发现了一些我认为可行但无法正确实施的方法。如果您能给我一些建议,我将不胜感激。

编辑: 我很抱歉,所以脚本确实可以识别文本文件:

YEAR1;PROJ1;
YEAR2;PROJ2;
YEAR3;PROJ3;

使用“;”作为它的分隔符。它确实在循环中运行,直到它完成的最后一个变量。但是,对于它的功能,我需要在文本文件中添加额外的行

YEAR4;PROJ4;
YEAR5;PROJ5;
end;

然后添加脚本

{ while IFS=';' read  YEAR1 PROJ1 YEAR2 PROJ2 YEAR3 PROJ3 YEAR4 PROJ4 YEAR5 PROJ5 fake
    do 
        { echo "$YEAR1" | egrep '^#|^ *$' ; } > /dev/null && continue 
            $local/script.sh \
                --forinput1 $YEAR1/$PROJ1  \
                --forinput2 $YEAR2/$PROJ2  \
                --forinput3 $YEAR3/$PROJ3  \
                --forinput4 $YEAR4/$PROJ4  \
                --forinput5 $YEAR5/$PROJ5  \
    done 
} < textFile.txt

我希望完成的是在数组中添加变量,但不必在脚本中添加额外的语法

这已经坏了,但我猜我在看什么

{ while IFS=';' read -a YEAR PROJ < textFile.txt
for ((i = 0; i < "${#YEAR[@]};${#PROJ[@]}"; i++)); do
{ echo "$YEAR[$i]" | egrep '^#|^ *$' ; } > /dev/null && continue 
                $local/script.sh \
                    --forinput[$i] ${YEAR[$i]}/${PROJ[$i]}  \
        done 
    }

【问题讨论】:

  • 您包含的read 命令似乎期望每行至少包含六个字段而不是两个字段。如果这确实是您的输入,并且您有多行记录,那么您可能应该包含更多记录并描述如何区分它们。另外,请包括您的预期输出。我们可以改进脚本的风格,但最好知道你想从中得到什么。
  • 您的循环将只运行一次,因为egrep 将在read 再次运行之前消耗textFile.txt 的其余部分。
  • 你真的想用文件中的所有参数调用一次 script.sh 吗?还是你有其他意图?
  • @chapner,真的吗? egrep 从 echo 语句中获取输入,然后退出。
  • 您可以将egrep '^#|^ *$' 测试移出循环:while ..;do..done &lt; &lt;(egrep -v '^#|^ *(;|$)' texttFile.txt)

标签: arrays bash loops sh


【解决方案1】:

我将假设您的输入文件包含 3 行每组 2 个字段,而不是 6 个字段的行。

$ cat file
y1a;p1a;
y2a;p2a;
y3a;p3a;
y1b;p1b;
y2b;p2b;
y3b;p3b;

然后,您可以将多个读取命令作为while“条件”:

while
     IFS=';' read year1 proj1 x
     IFS=';' read year2 proj2 x
     IFS=';' read year3 proj3 x
do
    echo script \
        --forinput1 "$year1/$proj1" \
        --forinput2 "$year2/$proj2" \
        --forinput3 "$year3/$proj3"
done < file
script --forinput1 y1a/p1a --forinput2 y2a/p2a --forinput3 y3a/p3a
script --forinput1 y1b/p1b --forinput2 y2b/p2b --forinput3 y3b/p3b

但是,这不处理 cmets 和空行。此版本在第 3 个非(注释/空白)行之后执行脚本

$ cat file
# set 1
y1a;p1a;
y2a;p2a;
y3a;p3a;

# set 2
y1b;p1b;
y2b;p2b;
y3b;p3b;

n=0
args=()
while IFS=';' read year project x; do
    { [[ $year == *([[:blank:]])"#"* ]] || [[ $year == *([[:blank:]]) ]]; } && continue
    ((n++))
    args+=( "--forinput$n" "$year/$project" )
    if (( n == 3 )); then 
        echo script "${args[@]}"
        args=()
        n=0
    fi
done < file
script --forinput1 y1a/p1a --forinput2 y2a/p2a --forinput3 y3a/p3a
script --forinput1 y1b/p1b --forinput2 y2b/p2b --forinput3 y3b/p3b

另一种处理每组任意记录的方法:

$ cat file
# set 1
y1a;p1a;
y2a;p2a;
y3a;p3a;
y4a;p4a;
y5a;p5a;
end;

# set 2
y1b;p1b;
y2b;p2b;
y3b;p3b;
end;

$ grep -Pv '^\s*(#|$)' file | awk -F"\n" -v RS="\nend;\n" -v OFS=, '{
    cmd = "script.sh"
    for (i=1; i<=NF; i++) {
        n = split($i, a, /;/)
        cmd = sprintf( "%s --forinput%d \"%s/%s\"", cmd, i, a[1], a[2])
    }
    print cmd
}'
script.sh --forinput1 "y1a/p1a" --forinput2 "y2a/p2a" --forinput3 "y3a/p3a" --forinput4 "y4a/p4a" --forinput5 "y5a/p5a"
script.sh --forinput1 "y1b/p1b" --forinput2 "y2b/p2b" --forinput3 "y3b/p3b"

使用grep 过滤掉 cmets 和空行。然后awk 来格式化命令:

  • RSrecord 分隔符,使用 end;
  • -F"\n"field 分隔符设置为换行符
  • 然后我们遍历字段数 (NF) 以构造您要运行的命令,并将其打印出来。

要实际执行它,请将 awk 输出通过管道传输到 | sh

【讨论】:

  • 谢谢格伦,老实说,它只保留一个字段,而我想要的是一个字段能够容纳我需要的尽可能多的行。我正在测试你的n=0 args=() while IFS=';' read year project x; do { [[ $year == *([[:blank:]])"#"* ]] || [[ $year == *([[:blank:]]) ]]; } &amp;&amp; continue ((n++)) args+=( "--forinput$n" "$year/$project" ) if (( n == 3 )); then echo script "${args[@]}" args=() n=0 fi done &lt; file,但我遇到了line 11: syntax error in conditional expression: unexpected token (['`你能澄清一下吗?
  • 文件中的end; 行是否用于分隔记录组?
  • 另外,对于“意外令牌”错误,您使用的是/bin/bash/bin/sh 还是其他shell?
  • 我喜欢你的三读方法。当我想到这样的解决方案时,我会处理循环外的特殊行(`
  • 谢谢,沃尔特 A。
【解决方案2】:

您的代码表明每个输入行有 6 个字段,这可能会导致类似的代码

sed -nr 's#([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);([^;]*);#$local/script.sh --forinput \1/\2 --forinput \3/\4 --forinput \5/\6#p' textFile.txt
# or shorter
f='([^;]*;)'
sed -nr 's#'$f$f$f$f$f$f'#$local/script.sh --forinput \1/\2 --forinput \3/\4 --forinput \5/\6#p' textFile.txt

当你必须组合 3 行输入时,你不应该尝试聪明。

# Don't do this
cat textFile.txt | paste -d'X' - - - | tr -d 'X'
# Trying to make
sed -nr 's#'$f$f$f$f$f$f'#$local/script.sh --forinput \1/\2 --forinput \3/\4 --forinput \5/\6#p' <(cat textFile.txt | paste -d'X' - - - | tr -d 'X')

在自豪地展示代码之后,您会发现,当第二行是注释(以“#”开头)时,您将不得不使代码变得更糟。您需要将cat textFile.txt 替换为grep -Ev '^#|^ *$' testFile.txt

当您需要关联不同的行时,请查看awk
没有检查的解决方案是

awk -F';' '{line++}
{param=param " --forinput " $1 "/" $2}
line==3 {print "$local/script.sh" param ; param=""; line=0}
' textFile.txt

您可以添加各种检查。

【讨论】:

    猜你喜欢
    • 2019-09-15
    • 2018-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多