在heredoc(EOF)中是否有类似于echo -n的内容?


12

我正在写一个庞大的脚本工厂脚本,为我的服务器生成许多维护脚本。

到现在为止,我写了一些行,需要用echo -ne例如一行

echo -n "if (( " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

# Generate exitCode check for each Server
IFS=" "
COUNT=0
while read -r name ipAddr
do
    if(($COUNT != 0))
    then
        echo -n " || " | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
    fi

    echo -n "(\$"$name"_E != 0 && \$"$name"_E != 1)" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

    COUNT=$((COUNT+1))

    JOBSDONE=$((JOBSDONE+1))
    updateProgress $JOBCOUNT $JOBSDONE
done <<< "$(sudo cat /root/.virtualMachines)"

echo " ))" | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null

这会生成我(如果我的配置文件中有例如2个服务器)代码,例如

if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))

所有其他不需要此内联代码编写的代码块都是我用heredocs生成的,因为我发现它们可以更好地编写和维护。例如,在上面的代码之后,我生成了其余的代码

cat << EOF | sudo tee -a /usr/local/bin/upgradeAllServers &> /dev/null
then
    # Print out ExitCode legend
    echo " ExitCode 42 - upgrade failed"
    echo " ExitCode 43 - Upgrade failed"
    echo " ExitCode 44 - Dist-Upgrade failed"
    echo " ExitCode 45 - Autoremove failed"
    echo ""
    echo ""
fi
EOF

所以最终的代码块看起来像

if (( ($server1_E != 0 && $server1_E != 1) || ($server2_E != 0 && $server2_E != 1) ))
then
    # Print out ExitCode legend
    echo " ExitCode 42 - upgrade failed"
    echo " ExitCode 43 - Upgrade failed"
    echo " ExitCode 44 - Dist-Upgrade failed"
    echo " ExitCode 45 - Autoremove failed"
    echo ""
    echo ""
fi

我的问题
是否有一种使Heredoc表现得与echo -ne没有行尾符号相似的方式?


1
如果您需要sudo脚本,那您就错了:以root身份运行整个脚本!
甜点

1
不,因为它也在做其他我不想以root用户身份运行的工作
derHugo

使用sudo -u USERNAME不是,看我如何运行脚本内部有一个“须藤”命令?
甜点,

和我一样,这样做有什么问题?
derHugo

4
好吧,当然有个问题:sudo没有用户名的脚本会要求输入密码,除非您输入密码有延迟。更糟糕的是,如果您不在终端中运行脚本,那么您甚至看不到密码查询,这将毫无用处。该sudo -u方法没有任何这些问题。
甜点

Answers:


9

不,heredoc总是以换行符结尾。但是您仍然可以删除最后一个换行符,例如使用Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -p逐行读取输入,运行中指定的代码-e,并打印(可能已修改)行
  • chomp删除最后一个换行符(如果存在),eof在文件末尾返回true。

1
@Yaron如果每个遇到的文件结尾(在这种情况下为封闭管道),它将从最后一行中截断换行符(这是heredoc和herestring自动添加的内容)
Sergiy Kolodyazhnyy

7

有没有一种方法,使Heredoc的行为类似于echo -ne而没有行尾符号?

简短的答案是heredoc和herestrings是通过这种方式构建的,所以不行-here-doc和herestring将始终添加尾随换行符,并且没有任何选项或本机方式来禁用它(也许将来的bash版本中还会有?)。

但是,仅通过bash方式进行处理的一种方法是通过标准while IFS= read -r方法读取Heredoc给出的内容,但增加延迟并通过末行打印printf而不用尾随换行符。这是仅使用bash的方法:

#!/usr/bin/env bash

while true
do
    IFS= read -r line || { printf "%s" "$delayed";break;}
    if [ -n "$delayed" ]; 
    then
        printf "%s\n" "$delayed"
    fi
    delayed=$line
done <<EOF
one
two
EOF

您在这里看到的是带有IFS= read -r line方法的常规while循环,但是每行都存储到变量中并因此而延迟(因此,我们跳过打印第一行,将其存储,仅在读取第二行后才开始打印)。这样,我们可以捕获最后一行,并且在下一次迭代时read返回退出状态1时,也就是我们通过打印不包含换行符的最后一行时printf。详细吗?是。但它有效:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

请注意$,由于没有换行符,提示字符被向前推送。作为奖励,这个解决方案是便携和POSIX十岁上下,所以它应该在工作kshdash为好。

$ dash ./strip_heredoc_newline.sh                                 
one
two$

6

我建议您尝试其他方法。而不是将多个echo语句和heredocs中生成的脚本拼凑在一起。我建议您尝试使用单个Heredoc。

您可以在heredoc中使用变量扩展和命令替换。像这个例子:

#!/bin/bash
cat <<EOF
$USER $(uname)
EOF

我发现,与逐段生成输出相比,这通常会导致可读性更高的最终结果。

同样,您似乎已经忘记了#!脚本开头的这一行。该#!行在任何格式正确的脚本中都是必需的。#!仅在从格式错误的脚本周围运行的shell调用该脚本时,才尝试不使用该行运行该脚本,并且即使在这种情况下,它最终也可能会被与您预期不同的shell解释。


还没有忘记,#!但是我的代码块只是2000行脚本中的
一小段

@derHugo使用命令替换需要循环的那部分是一种方法。另一种方法是在heredoc之前的变量中构造该部分。两者中哪一个最易读取决于循环中所需代码的长度,以及在相关行之前有多少Heredoc行。
卡巴斯德(Kasperd)'17

@derHugo调用脚本前面定义的shell函数的命令替换是第三种方法,如果需要大量代码来生成该行,则该方法可能更具可读性。
kasperd

@derHugo:请注意:(1)您可以有一个循环,将所有必需的命令放入变量中;(2)您可以$( ...command...)在Heredoc内部使用该命令,将这些命令的输出插入到Heredoc中的该位置;(3)Bash具有数组变量,您可以内联扩展它们,并在必要时使用替换在开始/结尾添加内容。这些加在一起使kasperd的建议更加强大(并且脚本可能更具可读性)。
psmears '17

由于它们的模板行为(wysiwyg),我正在使用Heredocs。在我看来,在另一个地方生成整个模板并比将其传递给Heredoc并没有真正使事情更易于阅读/维护。
derHugo

2

Heredocs总是以换行符结尾,但是您可以简单地删除它。我知道的最简单的方法是使用该head程序:

head -c -1 <<EOF | sudo tee file > /dev/null
my
heredoc
content
EOF

太酷了,我不知道它-c也需要负值!这样的甚至超过了pearl解决方案
derHugo

1

您可以按照自己喜欢的任何方式删除换行符,管道连接tr可能是最简单的。当然,这将删除所有换行符。

$ tr -d '\n' <<EOF 
if ((
EOF

但是,您要构建的if语句不需要这样做,(( .. ))即使算术表达式包含换行符也可以正常工作。

所以你可以

cat <<EOF 
if ((
EOF
for name in x y; do 
    cat << EOF
    ( \$${name}_E != 0 && \$${name}_E != 1 ) ||
EOF
done
cat <<EOF
    0 )) ; then 
    echo do something
fi
EOF

生产

if ((
    ( $x_E != 0 && $x_E != 1 ) ||
    ( $y_E != 0 && $y_E != 1 ) ||
    0 )) ; then 
    echo do something
fi

但是,将其循环构建仍然很丑陋。该|| 0是当然所以,我们并不需要特殊的情况下,第一或最后一行。


另外,您| sudo tee ...在每个输出中都具有该功能。我认为我们可以通过仅打开一次重定向(使用进程替换)并在以后的打印中使用它来摆脱这种情况:

exec 3> >(sudo tee outputfile &>/dev/null)
echo output to the pipe like this >&3
echo etc. >&3
exec 3>&-         # close it in the end

坦率地说,我仍然认为从外部脚本中的变量构建变量名(例如\$${name}_E)有点麻烦,应该用关联数组代替。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.