Có cái gì đó tương tự như echo -n trong heredoc (EOF) không?


12

Tôi đang viết trên một tập lệnh nhà máy kịch bản lớn tạo ra rất nhiều tập lệnh bảo trì cho các máy chủ của tôi.

Cho đến bây giờ tôi viết một số dòng cần được viết thành một dòng với echo -neví dụ

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

điều này tạo ra tôi (nếu có 2 máy chủ trong tệp cấu hình của tôi) mã như

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

Tất cả các khối mã khác không cần cách viết mã nội tuyến này do tôi sản xuất với heredocs vì tôi thấy chúng tốt hơn để viết và duy trì. Ví dụ: sau đoạn mã trên tôi có phần còn lại được tạo như

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

Vì vậy, khối mã cuối cùng trông giống như

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

Câu hỏi của tôi
Có cách nào để một di sản hành xử tương tự như echo -nekhông có biểu tượng cuối dòng không?


1
Nếu bạn cần sudotrong một tập lệnh, bạn đang làm sai: Thay vào đó, hãy chạy toàn bộ tập lệnh dưới dạng root!
tráng miệng

1
Không bởi vì nó cũng đang làm những thứ khác mà tôi không muốn chạy bằng root
derHugo

Thay sudo -u USERNAMEvào đó , hãy sử dụng lệnh 'sudo' bên trong tập lệnh? .
tráng miệng

vấn đề làm nó như tôi làm là gì?
derHugo

4
Tất nhiên, có một vấn đề: Một tập lệnh sudokhông có tên người dùng yêu cầu mật khẩu, trừ khi bạn nhập nó vào đó có độ trễ. Thậm chí còn tệ hơn nếu bạn không chạy tập lệnh trong một thiết bị đầu cuối, sau đó bạn thậm chí không thể nhìn thấy truy vấn mật khẩu và nó sẽ không hoạt động. Cách sudo -utiếp cận không có bất kỳ vấn đề nào trong số này.
tráng miệng

Câu trả lời:


9

Không, heredoc luôn kết thúc trong một dòng mới. Nhưng bạn vẫn có thể xóa dòng mới nhất, ví dụ như với Perl:

cat << 'EOF' | perl -pe 'chomp if eof'
First line
Second line
EOF
  • -pđọc dòng đầu vào theo dòng, chạy mã được chỉ định trong -evà in dòng (có thể đã sửa đổi)
  • chomp xóa dòng mới cuối cùng nếu nó tồn tại, eof trả về true ở cuối tệp.

1
@Yaron nếu mỗi lần gặp cuối tập tin (trong trường hợp này là đường ống đóng), nó sẽ kiểm tra char dòng mới từ dòng cuối cùng (đó là những gì heredoc và herestring thêm tự động)
Sergiy Kolodyazhnyy

7

Có cách nào để một di sản hành xử tương tự như echo -ne mà không có biểu tượng cuối dòng không?

Câu trả lời ngắn gọn là heredoc và herestrings chỉ được xây dựng theo cách đó, vì vậy không - ở đây-doc và herestring sẽ luôn luôn thêm một dòng mới và không có tùy chọn hoặc cách riêng nào để vô hiệu hóa nó (có lẽ sẽ có bản phát hành bash trong tương lai?).

Tuy nhiên, một cách để tiếp cận nó thông qua các cách chỉ bash sẽ là đọc những gì heredoc đưa ra thông qua while IFS= read -rphương thức tiêu chuẩn , nhưng thêm một độ trễ và in dòng cuối cùng thông qua printfmà không có dòng mới. Đây là những gì người ta có thể làm với chỉ 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

Những gì bạn thấy ở đây là vòng lặp while thông thường với IFS= read -r linecách tiếp cận, tuy nhiên mỗi dòng được lưu trữ vào một biến và do đó bị trì hoãn (vì vậy chúng tôi bỏ qua việc in dòng đầu tiên, lưu trữ và chỉ bắt đầu in khi chúng tôi đọc dòng thứ hai). Bằng cách đó, chúng ta có thể chụp dòng cuối cùng và khi lần lặp tiếp theo readtrả về trạng thái thoát 1, đó là khi chúng ta in dòng cuối cùng mà không có dòng mới thông qua printf. Dài dòng? Đúng. Nhưng nó đã có tác dụng:

$ ./strip_heredoc_newline.sh                                      
one
two$ 

Lưu ý rằng $ký tự nhanh chóng được đẩy về phía trước, vì không có dòng mới. Như một phần thưởng, giải pháp này là xách tay và POSIX-ish, vì vậy nó nên làm việc trong kshdashlà tốt.

$ dash ./strip_heredoc_newline.sh                                 
one
two$

6

Tôi khuyên bạn nên thử một cách tiếp cận khác. Thay vì chắp nối tập lệnh được tạo của bạn từ nhiều câu lệnh echo và heredocs. Tôi đề nghị bạn thử sử dụng một di truyền duy nhất.

Bạn có thể sử dụng mở rộng biến và thay thế lệnh bên trong một di sản. Giống như trong ví dụ này:

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

Tôi thấy rằng điều này thường dẫn đến kết quả cuối cùng dễ đọc hơn nhiều so với việc tạo ra từng mảnh đầu ra.

Ngoài ra, có vẻ như bạn đã quên #!dòng trong phần đầu của tập lệnh. Các #!dòng là bắt buộc trong bất kỳ kịch bản định dạng đúng. Cố gắng chạy tập lệnh mà không có #!dòng sẽ chỉ hoạt động nếu bạn gọi nó từ trình bao hoạt động xung quanh các tập lệnh được định dạng sai và thậm chí trong trường hợp đó, nó có thể sẽ bị dịch bởi một trình bao khác so với dự định của bạn.


chưa quên #!nhưng khối mã của tôi chỉ là một đoạn trong tập lệnh 2000 dòng;) Tôi không thấy ngay cách đề xuất của bạn giải quyết vấn đề của tôi khi tạo một dòng mã trong một vòng lặp ...
derHugo

@derHugo Thay thế lệnh cho một phần của dòng nơi bạn cần một vòng lặp là một cách để làm điều đó. Một cách khác là xây dựng phần đó trong một biến trước di truyền. Cái nào trong hai cái dễ đọc nhất phụ thuộc vào độ dài của mã cần thiết trong vòng lặp cũng như số lượng dòng di truyền mà bạn có trước dòng trong câu hỏi.
kasperd

@derHugo Thay thế lệnh gọi một hàm shell được xác định trước đó trong tập lệnh là cách tiếp cận thứ ba có thể dễ đọc hơn nếu bạn cần một lượng mã đáng kể để tạo một dòng đó.
kasperd

@derHugo: Lưu ý rằng: (1) Bạn có thể có một vòng lặp đặt tất cả các lệnh cần thiết vào một biến; (2) Bạn có thể sử dụng $( ...command...)bên trong một di sản, để chèn đầu ra của các lệnh đó vào dòng tại điểm đó trong di truyền; (3) Bash có các biến mảng, bạn có thể mở rộng nội tuyến và sử dụng các thay thế để thêm các thứ ở đầu / cuối nếu cần thiết. Cùng với những điều này làm cho gợi ý của kasperd mạnh mẽ hơn nhiều (và kịch bản của bạn có khả năng dễ đọc hơn nhiều).
psmears

Tôi đang sử dụng heredocs vì loại hành vi mẫu của họ (wysiwyg). Theo tôi, việc sản xuất toàn bộ khuôn mẫu ở một nơi khác và hơn là chuyển nó sang di sản theo quan điểm của tôi không thực sự làm cho mọi thứ dễ đọc / bảo trì hơn.
derHugo

2

Heredocs luôn kết thúc bằng một ký tự dòng mới nhưng bạn chỉ cần loại bỏ nó. Cách dễ nhất tôi biết là với headchương trình:

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

Thật tuyệt, tôi không biết rằng -ccũng có những giá trị tiêu cực! Như thế thậm chí còn hơn cả pearlgiải pháp
derHugo

1

Bạn có thể loại bỏ các dòng mới theo bất cứ cách nào bạn muốn, đường ống trcó thể là đơn giản nhất. Điều này sẽ loại bỏ tất cả các dòng mới, tất nhiên.

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

Tuy nhiên, nếu tuyên bố bạn xây dựng đang không đòi hỏi đó, biểu thức số học (( .. ))nên hoạt động tốt ngay cả khi nó có chứa ký tự dòng mới.

Vì vậy, bạn có thể làm

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

sản xuất

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

Xây dựng nó trong một vòng lặp vẫn làm cho xấu xí, mặc dù. Tất nhiên || 0là có để chúng ta không cần phải đặc biệt trường hợp đầu tiên hoặc dòng cuối cùng.


Ngoài ra, bạn có điều đó | sudo tee ...trong mỗi đầu ra. Tôi nghĩ rằng chúng ta có thể loại bỏ điều đó bằng cách chỉ mở một lần chuyển hướng (với sự thay thế quy trình) và sử dụng điều đó cho lần in sau:

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

Và thẳng thắn, tôi vẫn nghĩ rằng việc xây dựng tên biến từ các biến trong tập lệnh bên ngoài (như thế \$${name}_E) là một chút xấu xí, và nó có lẽ nên được thay thế bằng một mảng kết hợp .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.