Tại sao printf tốt hơn echo?


548

Tôi đã nghe nói rằng printftốt hơn echo. Tôi chỉ có thể nhớ lại một ví dụ từ kinh nghiệm của mình khi tôi phải sử dụng printfechokhông hoạt động để đưa một số văn bản vào một số chương trình trên RHEL 5.8 nhưng printfđã làm. Nhưng rõ ràng, có những khác biệt khác, và tôi muốn hỏi xem chúng là gì cũng như nếu có trường hợp cụ thể khi sử dụng cái này với cái kia.


Ai về echo -e?
neverMind9

1
@ neverMind9 echo -ekhông hoạt động sh, chỉ trong bash(vì một số lý do) và docker, ví dụ, sử dụng shtheo mặc định cho các RUNlệnh của nó
Ciprian Tomoiagă

@ CiprianTomoiagă echo -eđối với tôi hoạt động trong Android Terminal, về cơ bản là vậy sh.
neverMind9

Câu trả lời:


757

Về cơ bản, đó là một vấn đề về tính di động (và độ tin cậy).

Ban đầu, echokhông chấp nhận bất kỳ tùy chọn nào và không mở rộng bất cứ điều gì. Tất cả những gì nó đang làm là xuất ra các đối số được phân tách bằng một ký tự khoảng trắng và được chấm dứt bởi một ký tự dòng mới.

Bây giờ, ai đó nghĩ rằng sẽ rất tuyệt nếu chúng ta có thể làm những việc như echo "\n\t"xuất ký tự dòng hoặc tab mới hoặc có tùy chọn không xuất ký tự dòng mới.

Sau đó, họ suy nghĩ kỹ hơn nhưng thay vì thêm chức năng đó vào hệ vỏ (như perlnơi bên trong dấu ngoặc kép, \tthực sự có nghĩa là một ký tự tab), họ đã thêm nó vào echo.

David Korn đã nhận ra sai lầm và giới thiệu một hình thức trích dẫn vỏ mới: $'...'sau đó đã được sao chép bashzshnhưng nó đã quá muộn vào thời điểm đó.

Bây giờ khi một UNIX tiêu chuẩn echonhận được một đối số có chứa hai ký tự \tthay vì xuất ra chúng, nó sẽ xuất ra một ký tự tab. Và ngay khi nó nhìn thấy \ctrong một đối số, nó dừng xuất ra (vì vậy dòng mới theo dõi cũng không được xuất ra).

Các shell / nhà cung cấp / phiên bản Unix khác đã chọn làm điều đó một cách khác biệt: họ đã thêm một -etùy chọn để mở rộng các chuỗi thoát và một -ntùy chọn không xuất ra dòng mới. Một số có -Eđể vô hiệu hóa các chuỗi thoát, một số có -nnhưng không -e, danh sách các chuỗi thoát được hỗ trợ bởi một echotriển khai không nhất thiết giống như được hỗ trợ bởi một trình tự khác.

Sven Mascheck có một trang đẹp cho thấy mức độ của vấn đề .

Trên những echotriển khai hỗ trợ tùy chọn, có nói chung không có sự ủng hộ của một --để đánh dấu sự kết thúc lựa chọn (các echoBUILTIN của một số vỏ phi Bourne giống như làm, và zsh hỗ trợ -cho dù đó), vì vậy ví dụ, rất khó để đầu ra "-n"với echotrong nhiều vỏ.

Trên một số shell như bash¹ hoặc ksh93² hoặc yash( $ECHO_STYLEbiến), hành vi thậm chí phụ thuộc vào cách shell được biên dịch hoặc môi trường ( echohành vi của GNU cũng sẽ thay đổi nếu $POSIXLY_CORRECTở trong môi trường và với phiên bản 4 , zshvới bsd_echotùy chọn của phiên bản 4 , với tùy chọn của nó , một số pdksh dựa trên posixtùy chọn của họ hoặc liệu họ có được gọi là shhay không). Vì vậy, hai bash echos, thậm chí từ cùng một phiên bản bashkhông được đảm bảo hành xử giống nhau.

POSIX nói: nếu đối số đầu tiên là -nhoặc bất kỳ đối số nào chứa dấu gạch chéo ngược thì hành vi đó không được chỉ định . bashtiếng vang trong vấn đề đó không phải là POSIX, ví dụ như echo -ekhông xuất ra -e<newline>như POSIX yêu cầu. Đặc tả UNIX chặt chẽ hơn, nó cấm -nvà yêu cầu mở rộng một số chuỗi thoát bao gồm cả chuỗi \cđể dừng xuất.

Những thông số kỹ thuật đó không thực sự được giải cứu ở đây do nhiều triển khai không tuân thủ. Ngay cả một số hệ thống được chứng nhận như macOS 5 cũng không tuân thủ.

Để thực sự đại diện cho thực tế hiện tại, POSIX thực sự nên nói : nếu đối số đầu tiên khớp với biểu ^-([eEn]*|-help|-version)$thức chính mở rộng hoặc bất kỳ đối số nào có dấu gạch chéo ngược (hoặc các ký tự có mã hóa chứa mã hóa ký tự dấu gạch chéo ngược như αtrong các địa điểm sử dụng bảng mã BIG5), thì hành vi đó là không xác định.

Nói chung, bạn không biết cái gì echo "$var"sẽ xuất ra trừ khi bạn có thể chắc chắn rằng $varnó không chứa các ký tự dấu gạch chéo ngược và không bắt đầu bằng -. Đặc tả POSIX thực sự bảo chúng ta sử dụng printfthay thế trong trường hợp đó.

Vì vậy, điều đó có nghĩa là bạn không thể sử dụng echođể hiển thị dữ liệu không được kiểm soát. Nói cách khác, nếu bạn đang viết một tập lệnh và nó đang lấy đầu vào bên ngoài (từ người dùng làm đối số hoặc tên tệp từ hệ thống tệp ...), bạn không thể sử dụng echođể hiển thị tập lệnh.

Không sao đâu

echo >&2 Invalid file.

Đây không phải là:

echo >&2 "Invalid file: $file"

(Mặc dù nó sẽ hoạt động tốt với một số echotriển khai (không tuân thủ UNIX) như bashkhi xpg_echotùy chọn chưa được bật theo cách này hay cách khác như tại thời gian biên dịch hoặc qua môi trường).

file=$(echo "$var" | tr ' ' _)không phải là OK trong hầu hết các trường (trường hợp ngoại lệ là yashvới ECHO_STYLE=raw(với sự báo trước đó yashcủa các biến không thể giữ chuỗi tùy ý các byte do đó không tên tập tin tùy ý) và zsh's echo -E - "$var"6 ).

printf, mặt khác là đáng tin cậy hơn, ít nhất là khi nó bị giới hạn trong việc sử dụng cơ bản echo.

printf '%s\n' "$var"

Sẽ xuất nội dung $vartheo sau bởi một ký tự dòng mới bất kể nó có thể chứa ký tự nào.

printf '%s' "$var"

Sẽ xuất nó mà không có ký tự dòng mới.

Bây giờ, cũng có sự khác biệt giữa các printftriển khai. Có một lõi các tính năng được chỉ định bởi POSIX, nhưng sau đó có rất nhiều tiện ích mở rộng. Ví dụ, một số hỗ trợ a %qđể trích dẫn các đối số nhưng cách thực hiện thay đổi từ shell sang shell, một số hỗ trợ \uxxxxcho các ký tự unicode. Hành vi khác nhau đối với printf '%10s\n' "$var"các địa phương nhiều byte, có ít nhất ba kết quả khác nhau choprintf %b '\123'

Nhưng cuối cùng, nếu bạn sử dụng bộ tính năng POSIX printfvà không thử làm bất cứ điều gì quá lạ mắt với nó, bạn sẽ không gặp rắc rối.

Nhưng hãy nhớ đối số đầu tiên là định dạng, vì vậy không nên chứa dữ liệu biến / không kiểm soát.

Một cách đáng tin cậy hơn có echothể được thực hiện bằng cách sử dụng printf, như:

echo() ( # subshell for local scope for $IFS
  IFS=" " # needed for "$*"
  printf '%s\n' "$*"
)

echo_n() (
  IFS=" "
  printf %s "$*"
)

echo_e() (
  IFS=" "
  printf '%b\n' "$*"
)

Subshell (ngụ ý sinh ra một quá trình bổ sung trong hầu hết các triển khai shell) có thể tránh được bằng cách sử dụng local IFSvới nhiều shell hoặc bằng cách viết nó như sau:

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
  fi
  if [ "$#" -gt 0 ]; then
     printf ' %s' "$@"
  fi
  printf '\n'
}

Ghi chú

1. hành vi bashcủa họ echocó thể được thay đổi như thế nào .

Với bash, trong thời gian chạy, có hai điều khiển hành vi của echo(bên cạnh enable -n echohoặc xác định lại echodưới dạng hàm hoặc bí danh): xpg_echo bashtùy chọn và liệu bashcó ở chế độ posix hay không. posixchế độ có thể được bật nếu bashđược gọi là shhoặc nếu POSIXLY_CORRECTtrong môi trường hoặc với posixtùy chọn:

Hành vi mặc định trên hầu hết các hệ thống:

$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character

xpg_echo mở rộng các chuỗi như UNIX yêu cầu:

$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A

Nó vẫn tôn vinh -n-e(và -E):

$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%

Với xpg_echochế độ POSIX:

$ env BASHOPTS=xpg_echo POSIXLY_CORRECT=1 bash -c 'echo -n "\0101"'
-n A
$ env BASHOPTS=xpg_echo sh -c 'echo -n "\0101"' # (where sh is a symlink to bash)
-n A
$ env BASHOPTS=xpg_echo SHELLOPTS=posix bash -c 'echo -n "\0101"'
-n A

Lần này, bashlà cả POSIX và UNIX phù hợp. Lưu ý rằng trong chế độ POSIX, bashvẫn không tuân thủ POSIX vì nó không xuất ra -etrong:

$ env SHELLOPTS=posix bash -c 'echo -e'

$

Các giá trị mặc định cho xpg_echo và posix có thể được xác định tại thời điểm biên dịch với --enable-xpg-echo-default--enable-strict-posix-defaultcác tùy chọn cho configuretập lệnh. Đó thường là những phiên bản gần đây của OS / X làm gì để xây dựng chúng /bin/sh. Mặc dù vậy, không có triển khai / phân phối Unix / Linux nào trong tâm trí của họ thường sẽ làm điều đó/bin/bash . Trên thực tế, điều đó không đúng, việc /bin/bashOracle vận chuyển với Solaris 11 (trong gói tùy chọn) dường như được xây dựng cùng --enable-xpg-echo-default(đó không phải là trường hợp trong Solaris 10).

2. Làm thế nào ksh93's echohành vi có thể được thay đổi.

Trong ksh93, việc echomở rộng các chuỗi thoát hay không và nhận ra các tùy chọn tùy thuộc vào nội dung của các biến $PATHvà / hoặc $_AST_FEATURESmôi trường.

Nếu $PATHchứa một thành phần có chứa /5binhoặc /xpgtrước /binhoặc /usr/binthành phần thì nó hoạt động theo cách SysV / UNIX (mở rộng trình tự, không chấp nhận các tùy chọn). Nếu nó tìm thấy /ucbhoặc /bsdđầu tiên hoặc nếu $_AST_FEATURES7 chứa UNIVERSE = ucb, thì nó hoạt động theo cách BSD 3 ( -eđể cho phép mở rộng, nhận dạng -n).

Mặc định là phụ thuộc hệ thống, BSD trên Debian (xem đầu ra của builtin getconf; getconf UNIVERSEcác phiên bản gần đây của ksh93):

$ ksh93 -c 'echo -n' # default -> BSD (on Debian)
$ PATH=/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /xpg before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH ksh93 -c 'echo -n' # /5bin before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH _AST_FEATURES='UNIVERSE = ucb' ksh93 -c 'echo -n' # -> BSD
$ PATH=/ucb:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /ucb first -> BSD
$ PATH=/bin:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /bin before /xpg -> default -> BSD

3. BSD cho tiếng vang -e?

Tham chiếu đến BSD để xử lý -etùy chọn là một chút sai lệch ở đây. Hầu hết các echohành vi khác nhau và không tương thích đều được giới thiệu tại AT & T:

  • \n, \0ooo, \cTrong Bench Programmer của làm việc UNIX (dựa trên Unix V6), và phần còn lại ( \b, \r...) trong Unix Hệ thống III Ref .
  • -ntrong Unix V7 (bởi Dennis Ritchie Ref )
  • -etrong Unix V8 (bởi Dennis Ritchie Ref )
  • -Ebản thân nó có thể ban đầu đến từ bash(CWRU / CWRU.chlog trong phiên bản 1.13.5 đề cập Brian Fox thêm vào ngày 1992-10-18, GNU echosao chép nó ngay sau đó trong sh-utils-1.8 được phát hành 10 ngày sau đó)

Mặc dù phần echotích hợp của shhoặc BSD đã hỗ trợ -ekể từ ngày họ bắt đầu sử dụng trình bao Almquist cho nó vào đầu những năm 90, echotiện ích độc lập cho đến ngày nay không hỗ trợ nó ở đó ( FreeBSDecho vẫn không hỗ trợ -e, mặc dù nó hỗ trợ -nnhư Unix V7 (và cũng có \cnhưng chỉ ở phần cuối của đối số cuối cùng)).

Việc xử lý -eđược bổ sung vào ksh93's echokhi trong BSD vũ trụ trong phiên bản ksh93r phát hành vào năm 2006 và có thể được vô hiệu hóa tại thời gian biên dịch.

4. GNU echo thay đổi hành vi trong 8.31

Kể từ coreutils 8,31 (và cam kết này ), GNU echobây giờ mở rộng chuỗi escape theo mặc định khi POSIXLY_CORRECT là trong môi trường, để phù hợp với hành vi của bash -o posix -O xpg_echo's echoBUILTIN (xem báo cáo lỗi ).

5. macOS echo

Hầu hết các phiên bản macOS đã nhận được chứng nhận UNIX từ Opengroup .

Của họ shđược xây dựng trong echotương thích như nó bash(một phiên bản rất cũ) xây dựng với xpg_echokích hoạt theo mặc định, nhưng độc lập của họ echotiện ích không phải là. env echo -nđầu ra không có gì thay vì -n<newline>, env echo '\n'đầu ra \n<newline>thay vì <newline><newline>.

Đó /bin/echolà một từ FreeBSD ngăn chặn đầu ra dòng mới nếu đối số đầu tiên là -nhoặc (kể từ năm 1995) nếu đối số cuối cùng kết thúc \c, nhưng không hỗ trợ bất kỳ chuỗi dấu gạch chéo ngược nào khác được UNIX yêu cầu, thậm chí không \\.

6. echotriển khai có thể xuất nguyên văn dữ liệu tùy ý

Nói đúng ra, bạn cũng có thể đếm mà FreeBSD / MacOS /bin/echotrên (không vỏ của họ echođược xây dựng trong), nơi zsh's echo -E - "$var"hoặc yash' s ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") có thể được viết như sau:

/bin/echo "$var
\c"

Việc triển khai hỗ trợ -E-n(hoặc có thể được cấu hình) cũng có thể thực hiện:

echo -nE "$var
"

zsh's echo -nE - "$var"( printf %s "$var") có thể được viết

/bin/echo "$var\c"

7. _AST_FEATURESvà ASTUNIVERSE

Điều _AST_FEATURESnày không có nghĩa là được thao tác trực tiếp, nó được sử dụng để tuyên truyền các cài đặt cấu hình AST trong suốt quá trình thực thi lệnh. Cấu hình được thực hiện thông qua astgetconf()API (không có giấy tờ) . Bên trong ksh93, getconfnội dung (được kích hoạt bằng builtin getconfhoặc bằng cách gọi command /opt/ast/bin/getconf) là giao diện đểastgetconf()

Chẳng hạn, bạn sẽ làm gì builtin getconf; getconf UNIVERSE = attđể thay đổi UNIVERSEcài đặt thành att(gây ra echohành vi theo cách SysV trong số những thứ khác). Sau khi làm điều đó, bạn sẽ nhận thấy $_AST_FEATURESbiến môi trường chứa UNIVERSE = att.


13
Rất nhiều sự phát triển unix sớm đã xảy ra trong sự cô lập và các nguyên tắc kỹ thuật phần mềm tốt như 'khi bạn thay đổi giao diện, thay đổi tên' không được áp dụng.
Henk Langeveld

7
Một lưu ý, lợi thế duy nhất (và có lẽ là duy nhất) của việc echomở rộng các \xchuỗi trái ngược với vỏ như một phần của cú pháp trích dẫn là sau đó bạn có thể xuất ra một byte NUL (một thiết kế sai khác được cho là của Unix là các phân tách không hợp lệ các chuỗi, trong đó một nửa các cuộc gọi hệ thống (như execve()) không thể thực hiện các chuỗi byte tùy ý)
Stéphane Chazelas

Như bạn có thể thấy trên trang web từ Sven Maschek, thậm chí printfdường như là một vấn đề vì hầu hết các triển khai đều làm sai. Việc triển khai đúng duy nhất tôi biết là trong boshvà trang từ Sven Maschek không liệt kê các vấn đề với byte rỗng thông qua \0.
schily

1
Đây là một câu trả lời tuyệt vời - cảm ơn @ StéphaneChazelas đã viết nó lên.
JoshuaRLi

28

Bạn có thể muốn sử dụng printfcho các tùy chọn định dạng của nó. echorất hữu ích khi in giá trị của một biến hoặc một dòng (đơn giản), nhưng đó là tất cả những gì có trong đó. printfvề cơ bản có thể làm những gì phiên bản C của nó có thể làm.

Ví dụ sử dụng và khả năng:

Echo:

echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf:

vech="bike"
printf "%s\n" "$vech"

Nguồn:


@ 0xC0000022L Tôi đứng sửa lời cảm ơn. Tôi không nhận thấy tôi đã liên kết đến trang web sai khi vội vàng trả lời câu hỏi. Cảm ơn bạn đã đóng góp của bạn và sửa chữa.
NlightNFotis

7
Sử dụng echođể in một biến có thể thất bại nếu giá trị của biến chứa các ký tự meta.
Keith Thompson

17

Một "lợi thế", nếu bạn muốn gọi nó là, bạn sẽ không phải nói nó muốn echodiễn giải các chuỗi thoát nhất định như \n. Nó biết giải thích chúng và sẽ không yêu cầu -ephải làm như vậy.

printf "some\nmulti-lined\ntext\n"

(NB: cuối cùng \nlà cần thiết, echongụ ý nó, trừ khi bạn đưa ra -ntùy chọn)

đấu với

echo -e "some\nmulti-lined\ntext"

Lưu ý cuối cùng \ntrong printf. Vào cuối ngày, đó là vấn đề về hương vị và yêu cầu những gì bạn sử dụng: echohoặc printf.


1
Đúng cho /usr/bin/echobashdựng sẵn. Các dash, kshzshđược xây dựng trong echokhông cần phải -echuyển sang mở rộng ký tự gạch chéo ngược thoát.
thao tác

Tại sao báo giá sợ? Từ ngữ của bạn ngụ ý rằng nó không nhất thiết là một lợi thế thực sự.
Keith Thompson

2
@KeithThndry: thực ra tất cả những gì họ muốn ám chỉ là không phải ai cũng có thể coi đó là một lợi thế.
0xC0000022L

bạn có thể mở rộng về điều đó? Tại sao nó không phải là một lợi thế? Cụm từ "nếu bạn muốn gọi nó là" ngụ ý khá mạnh mẽ mà bạn nghĩ rằng nó không phải.
Keith Thompson

2
Hoặc bạn có thể làm printf '%s\n' 'foo\bar.
nyuszika7h

6

Một nhược điểm của printfhiệu năng là vì vỏ tích hợp echonhanh hơn nhiều. Điều này xuất hiện đặc biệt ở Cygwin nơi mỗi phiên bản của một lệnh mới gây ra tình trạng nặng nề cho Windows. Khi tôi thay đổi chương trình nặng tiếng vang của mình từ sử dụng /bin/echosang tiếng vang của vỏ, hiệu suất tăng gần gấp đôi. Đó là một sự đánh đổi giữa tính di động và hiệu suất. Nó không phải là một slam dunk để luôn luôn sử dụng printf.


15
printfđược xây dựng trong hầu hết các shell hiện nay (bash, dash, ksh, zsh, yash, một số dẫn xuất pdksh ... vì vậy bao gồm cả các shell thường được tìm thấy trên cygwin). Các ngoại lệ đáng chú ý duy nhất là một số pdkshdẫn xuất.
Stéphane Chazelas

Nhưng nhiều trong số các triển khai printf bị hỏng. Điều này rất cần thiết khi bạn muốn sử dụng printfđể xuất các byte nul, nhưng một số triển khai diễn giải `\ c 'trong chuỗi định dạng mặc dù chúng không nên.
schily

2
@schily, hành vi của printfPOSIX không được chỉ định nếu bạn sử dụng \ctrong chuỗi định dạng, vì vậy printfviệc triển khai có thể làm bất cứ điều gì họ muốn trong vấn đề đó. Chẳng hạn, một số đối xử với nó giống như trong PWB echo(khiến printf thoát ra), ksh sử dụng nó cho các \cAký tự điều khiển (cho đối số định dạng printf và $'...'không phải cho echocả print!). Bạn không chắc chắn những gì mà đã làm với in NUL byte, hoặc có thể bạn đang đề cập đến ksh's printf '\c@'?
Stéphane Chazelas
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.