Discussion:
[Python] Konvertering af tegnsæt for stdio
(for gammel til at besvare)
Klaus Alexander Seistrup
2009-01-10 01:23:36 UTC
Permalink
Hej gruppe

Jeg har et binært program der forventer input i ISO-8859-1, og som
osse leverer uddata i ISO-8859-1. Jeg skal nu bruge programmet på
en linuxbaseret pc der kører UTF-8 som tegnsæt, og jeg tænkte at
det vil være enklere at skrive et wrapper-script til programmet end
at skulle skrive hele programmet om (det behandler osse data internt
i ISO-8859-1, så det er ikke helt enkelt at skulle skrive rutinerne
om, selv om programmet ikke er så stort).

Nuvel, taktikken er at konvertere kommandolinjeargumenterne manuelt,
og så bruge codecs.EncodedFile() til at sørge for at stdio er ind-
kodet med det korrekte tegnsæt på det rigtige sted. Det virker
næsten.

Programmet ser således ud i barberet udgave:

#v+
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import os
import locale
import codecs
import subprocess

DEBUG = os.environ.get('DEBUG', False)

locale.setlocale(locale.LC_ALL, '')
encoding = locale.getlocale()[1] or 'utf-8'

if DEBUG:
print encoding, locale.getpreferredencoding()

args = map(lambda s: unicode(s, encoding).encode('iso-8859-1'), sys.argv)

# Her er det program som skal wrappes:
args[0] = '/usr/bin/programnavn'

fi = codecs.EncodedFile(sys.stdin, encoding, 'iso-8859-1', errors='replace')
fo = codecs.EncodedFile(sys.stdout, 'iso-8859-1', encoding, errors='replace')
fe = codecs.EncodedFile(sys.stderr, 'iso-8859-1', encoding, errors='replace')

if DEBUG:
for arg in args:
fo.write(arg + '\n')

sys.exit(subprocess.call(args, stdin=fi, stdout=fo, stderr=fe))

# eof
#v-

Det er ganske let at forvisse sig om at de tre streams (fi, fo & fe)
fungerer efter hensigten. Problemet synes at være at call() helt
ignorerer de filepointere jeg giver den.

Er der nogen der kan greje hvorfor det går galt, eller som har et
forslag til en løsning der virker?

På forhånd tak for hjælpen.

PS: Som kuriositet er her en wrapper skrevet i bash som virker:

#v+
#!/bin/bash

declare -a args
declare -i i=0

for arg in "${@}"
do
args[${i}]="$(iconv -f utf-8 -t iso-8859-1 <<< ${arg})"
i=$((i+1))
done

/usr/bin/programnavn "${args[@]}" | iconv -f iso-8859-1 -t utf-8
:
# eof
#v-

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Anders J. Munch
2009-01-10 10:52:31 UTC
Permalink
Post by Klaus Alexander Seistrup
fi = codecs.EncodedFile(sys.stdin, encoding, 'iso-8859-1', errors='replace')
fo = codecs.EncodedFile(sys.stdout, 'iso-8859-1', encoding, errors='replace')
fe = codecs.EncodedFile(sys.stderr, 'iso-8859-1', encoding, errors='replace')
fo.write(arg + '\n')
sys.exit(subprocess.call(args, stdin=fi, stdout=fo, stderr=fe))
Fra TFM:
"Valid values are PIPE, an existing file descriptor (a positive integer), an
existing file object, and None. PIPE indicates that a new pipe to the child
should be created."

codecs.EncodedFile er ingen af delene. Det underliggende systemkald skal bruge
en fil-descriptor. Det kan det få fra en file med .fileno, men
codecs.EncodedFile er ikke rigtige file's men fil-lignende objekter uden egen
fil-descriptor, så deres .fileno-metode delegerer bare videre til den wrappede fil.
Kuriositet? Hvis du har en fungerende løsning, hvad er så problemet?
Okay, jeg kan se det ikke er helt færdigt: det er kun stdout der konverteres.
Men forudsat at du ikke har brug for at holde stdout og stderr adskilt, så er
det vel hurtigt rettet.

I øvrigt kan du bruge samme fremgangsmåde i Python: 3 x subprocess.call, trådet
sammen med PIPEs. Eller måske snarere 4 x subprocess.call, så stdout og stderr
får separate iconv konverteringer, og stdout og stderr holdes adskilt.

mvh. Anders
Klaus Alexander Seistrup
2009-01-10 11:18:08 UTC
Permalink
"Valid values are PIPE, an existing file descriptor [...]
codecs.EncodedFile er ingen af delene. Det underliggende system-
kald skal bruge en fil-descriptor.
Tak, det hjælper med flere øjne på sagen - jeg havde overset at der
stod 'file descriptor', og ikke 'file pointer'. Så forstår jeg meget
bedre!
Kuriositet? Hvis du har en fungerende løsning, hvad er så
problemet?
Som du selv skrev (men som jeg ikke har citeret): (1) at stdout og
stderr ikke holdes adskilte, og (2) at der ikke tages hensyn til at
brugeren kan benytte andre indkodninger end 'utf-8'.
I øvrigt kan du bruge samme fremgangsmåde i Python: 3 x sub-
process.call, trådet sammen med PIPEs. Eller måske snarere 4 x
subprocess.call, så stdout og stderr får separate iconv konverteringer,
og stdout og stderr holdes adskilt.
Jeg har klaret det med et enkelt kald til subprocess.Popen(), der har
PIPE som stdio, og så looper jeg efterfølgende stdout og stderr igennem.
Forkortet udgave uden fejltjek:

#v+

encode = lambda s: unicode(s, 'iso-8859-1').encode(encoding)

proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)

for line in proc.stdout:
sys.stdout.write(encode(line))

for line in proc.stderr:
sys.stderr.write(encode(line))

#v-

(Man ku' sikkert godt lave noget med map() og .writelines() hvis man
er i dét humør.)

Det ville bare ha' været mere elegant hvis codecs.EncodedFile() havde
virket, synes jeg. Men så igen: Hvis alting havde været anderledes,
ville alting have været anderledes.

Tak for dit input.

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Klaus Alexander Seistrup
2009-01-11 00:08:00 UTC
Permalink
Jeg har klaret det med et enkelt kald til subprocess.Popen(), [...]
Hvis nogen er interesseret, kom programmet til at se således ud i sin
helhed:

· http://bit.ly/cVtU (kort link til gitorious.org)
· http://bit.ly/rrJh (link til projektets oversigtsside)

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Loading...