Mengapa Popen.communicate () mengembalikan b'hi \ n 'bukan' hi '?

92

Adakah yang bisa menjelaskan mengapa hasil yang saya inginkan, "hai", diawali dengan huruf 'b' dan diikuti dengan baris baru?

Saya menggunakan Python 3.3

>>> import subprocess
>>> print(subprocess.Popen("echo hi", shell=True,
                           stdout=subprocess.PIPE).communicate()[0])
b'hi\n'

'B' ekstra ini tidak muncul jika saya menjalankannya dengan python 2.7

imaginerThat
sumber
1
Versi Python apa yang Anda gunakan?
Necrolyte2
2
Tidak yakin tentang 'b', tetapi baris baru karena echo hicetakan hi\r\n. Untuk menghindarinya, Anda bisa menambahkan .strip () di akhir, atau perbaikan serupa.
azhrei
7
Anda dapat menggunakan check_output()alih-alih di .communicate()sini:print(subprocess.check_output("echo hi", shell=True, universal_newlines=True), end="")
jfs

Jawaban:

22

Perintah echo secara default mengembalikan karakter baris baru

Bandingkan dengan ini:

print(subprocess.Popen("echo -n hi", \
    shell=True, stdout=subprocess.PIPE).communicate()[0])

Adapun b sebelum string itu menunjukkan bahwa itu adalah urutan byte yang setara dengan string normal di Python 2.6+

http://docs.python.org/3/reference/lexical_analysis.html#literals

Necrolyte2
sumber
5
Anda tidak perlu '\' di dalam tanda kurung.
jfs
94

Ini bmenunjukkan bahwa apa yang Anda miliki adalah bytes, yang merupakan urutan byte biner, bukan string karakter Unicode. Subproses mengeluarkan byte, bukan karakter, jadi itulah yang ditampilkan communicate().

The bytestipe tidak langsung print()mampu, jadi Anda sedang menunjukkan reprdari bytesyang Anda miliki. Jika Anda mengetahui pengkodean byte yang Anda terima dari subproses, Anda dapat menggunakan decode()untuk mengubahnya menjadi dapat dicetak str:

>>> print(b'hi\n'.decode('ascii'))
hi

Tentu saja, contoh khusus ini hanya berfungsi jika Anda benar-benar menerima ASCII dari subproses. Jika bukan ASCII, Anda akan mendapatkan pengecualian:

>>> print(b'\xff'.decode('ascii'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xff in position 0…

Baris baru adalah bagian dari apa yang echo himemiliki keluaran. echoTugasnya adalah menampilkan parameter yang Anda lewati, diikuti dengan baris baru. Jika Anda tidak tertarik dengan spasi yang mengelilingi keluaran proses, Anda dapat menggunakan strip()seperti ini:

>>> b'hi\n'.strip()
b'hi'
zigg
sumber
1
Bagaimana Anda mendapatkan fungsi print () untuk mencetak string byte tanpa 'b' sebelumnya? Atau apakah Anda perlu mengubahnya menjadi string unicode terlebih dahulu?
Imagineer Itu
Saya penasaran, ketika os.popenmengembalikan string teks, apakah ada cara untuk membuatnya subprocess.Popenjuga mengembalikannya, bukan string byte.
Pavel Šimerda
11
Saya akan menjawab sendiri, ada opsi dengan nama samar yang disebut universal_newlinesyang menyebabkan Popenobjek menerima dan mengembalikan string teks.
Pavel Šimerda
3
@ PavelŠimerda Sementara os.popen mengembalikan string teks, mereka tampaknya sedang diterjemahkan secara tidak benar untuk karakter non-ascii, setidaknya pada Windows. Misalnya menjalankan check_output("dir"), mengekstrak nama file dari output dan kemudian mencoba mengaksesnya dengan openakan gagal jika nama file tersebut berisi umlaut Jerman. Mungkin bug.
kdb
57

Seperti yang disebutkan sebelumnya, echo hisebenarnya kembali hi\n, yang merupakan perilaku yang diharapkan.

Tetapi Anda mungkin hanya ingin mendapatkan data dalam format yang "benar" dan tidak berurusan dengan pengkodean. Yang perlu Anda lakukan adalah memberikan universal_newlines=Trueopsi untuk subprocess.Popen()menyukainya:

>>> import subprocess
>>> print(subprocess.Popen("echo hi",
                           shell=True,
                           stdout=subprocess.PIPE,
                           universal_newlines=True).communicate()[0])
hi

Cara ini Popen()akan menggantikan simbol yang tidak diinginkan dengan sendirinya.

Danil
sumber
11
universal_newlines=Truebekerja seperti pesona. Ini harus menjadi jawaban yang diterima, menurut pendapat saya ...
Ethan Strider
3
Ini menghasilkan baris kosong ekstra.
LoMaPh
1
Anda mungkin perlu baik universal_newlines=True dalam Popen(untuk menyingkirkan b'') dan strip()pada string yang dihasilkan, jika Anda ingin memotong baris terminating.
arielf
FYI, dokumentasi mengatakan universal_newlinessekarang hanya alias yang kompatibel ke belakang untuk textparameter, yang lebih jelas tetapi hanya di Python 3.7 dan di atasnya.
Harry Cutts
Ini menghasilkan baris kosong ekstra karena tidak berfungsi. universal_newlines tidak menghapus \ n
kol23
8

b adalah representasi byte dan \ n adalah hasil dari output echo.

Berikut ini hanya akan mencetak data hasil

import subprocess
print(subprocess.Popen("echo hi", shell=True,stdout=subprocess.PIPE).communicate()[0].decode('utf-8').strip())
Jenish
sumber