Windows上Python读取stdin出错

本文介绍了一段用于读取stdin并在stdout上输出替换后内容的Python代码,在Windows环境下使用cmd.exe时遇到的问题及解决方法。该问题源于Windows cmd.exe对文件关联的处理方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Windows上Python读取stdin出错

(金庆的专栏)

一段简单的代码,读取stdin, 替换输出到stdout:

#!/usr/bin/env python
import os, sys
input_file = sys.stdin
output_file = sys.stdout
for s in input_file:
    output_file.write(s.replace("abc", "def"))

代码改自CookBook的    
Recipe 2.3. Searching and Replacing Text in a File

在Linux上运行正常:
$ cat input.txt | ./replace.py
 
但是在Windows的Dos窗口运行会报错:
> type input.txt | replace.py
Traceback...
    for s in input_file:
IOError: [Errno 9] Bad file descriptor

如果用python调用就正常:
> type input.txt | python replace.py

原因是cmd.exe通过后缀关联运行py文件存在问题。
详见:Bad pipe filedescriptor when reading from stdin in python
http://stackoverflow.com/questions/1057638/bad-pipe-filedescriptor-when-reading-from-stdin-in-python

It seems that stdin/stdout redirect does not work when starting from a file association. This is not specific to python, but a problem caused by win32 cmd.exe.


<think>我们有一个需求:在Shell脚本中读取Python语句生成的JSON数据,并解析该JSON数据。 根据引用[1][2][3]中的示例,我们可以通过管道将Python输出的JSON传递给另一个Python脚本来解析,或者使用其他工具(如jq),但这里用户明确提到在Shell脚本中解析Python输出的JSON。 常见方案: 1. 使用Python的内置JSON模块解析(如引用中所示),通过-c参数执行简短代码。 2. 使用jq工具(如果系统安装)可以更简洁地处理,但用户没有提到,且引用中都是使用Python解析。 根据引用[1]和[3]的示例,我们可以这样操作: - 将Python程序生成的JSON通过管道传递给另一个Python命令进行解析。 - 在Shell脚本中,我们可以将Python程序输出JSON的代码放在一个子进程中,然后通过管道传递给解析的Python命令。 示例场景: 假设我们有一个Python程序(比如叫generate_data.py)输出JSON字符串:{"name": "Alice", "age": 30} 我们想在Shell脚本中获取name和age的值。 步骤: 1. 确保Python环境可用(引用[2]中检查了python是否存在)。 2. 使用管道将生成JSON的Python命令和解析JSON的Python命令连接。 方法一:直接使用管道和Python内联命令解析 生成JSON的Python命令示例(模拟): python3 -c "import json; print(json.dumps({'name': 'Alice', 'age': 30}))" 解析并获取name的Shell命令(使用另一个Python内联命令): python3 -c "import json; print(json.dumps({'name': 'Alice', 'age': 30}))" | \ python3 -c "import sys, json; data=json.load(sys.stdin); print(data['name'])" 输出:Alice 方法二:将解析写成Shell函数(如引用[2]所示) 我们可以定义一个函数,专门用来从stdin读取JSON并提取指定的键值。 例如,定义一个函数get_json_key: get_json_key() { python3 -c "import sys, json; print(json.load(sys.stdin).get('$1', ''))" } 然后这样使用: json_data=$(python3 -c "import json; print(json.dumps({'name': 'Alice', 'age': 30}))") # 或者直接通过管道 name=$(echo "$json_data" | get_json_key 'name') age=$(echo "$json_data" | get_json_key 'age') 但是注意:引用[2]中的函数定义,它使用了`python`命令,并且处理了路径问题(awk部分),但我们的例子中不需要,因为直接使用python3。 注意点: 1. 确保JSON字符串的编码正确(引用[3]中设置了PYTHONIOENCODING=utf8),避免中文等乱码。 2. 如果JSON数据量较大,或者有嵌套结构,需要适当处理(如引用[3]中获取嵌套字段:`data['data']['ip']`)。 复杂嵌套解析示例: 假设JSON为:{"person": {"name": "Alice", "age": 30}} 要获取name:`data['person']['name']` 在Shell函数中,我们可以传递一个键的路径,但函数需要支持嵌套。但是,简单的函数只支持一级键(如引用[2]中的函数)。我们可以改进函数使其支持点分路径。 改进的函数(支持点分路径,如'person.name'): get_json_value() { local key_path="$1" # 将点分路径转换为多个键的列表,并用']['.join local python_code="import sys, json; data=json.load(sys.stdin)" # 分割key_path IFS='.' read -ra keys <<< "$key_path" for k in "${keys[@]}"; do python_code+=" = data['$k']" done python_code+="; print(data)" # 但是这样写循环构造代码有误,应该使用递归或逐步深入 # 更简单:使用一个循环来逐步获取值 # 重构:使用一个表达式一次性获取 # 另一种方法:使用jsonpath,但这里不用,我们直接构造表达式 # 例如:key_path="person.name",则表达式:data['person']['name'] # 我们可以:key_expr="".joinjoin(f"['{k}']" for k in keys) # 但是这样有安全问题(如果key中包含单引号等),所以最好用json.loads然后逐步获取,但内联代码不方便。 # 改为:在Python代码中拆分路径 python3 -c "import sys, json; data = json.load(sys.stdin); keys = '$key_path'.split('.'); for key in keys: data = data[key]; print(data)" } 但是注意:如果key_path中包含空格或特殊字符,这种方法可能不安全。更安全的方式是将路径作为参数传递给Python,然后在Python中拆分。 使用示例: value=$(echo '{"person": {"name": "Alice"}}' | get_json_value "person.name") 然而,引用中的示例都是简单的单层键。对于多层,我们可以参考引用[3]的方式,在Python命令中直接写表达式。 因此,如果解析多层,建议直接在Python命令中写清楚路径,如: curl ... | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['ip']" 所以,如果需求是固定的,可以直接在命令中写路径;如果需要灵活,可以写一个函数接受点分路径。 总结步骤: 步骤1:在Shell脚本中,通过Python命令生成JSON数据(可以是另一个Python脚本,也可以是内联命令)。 步骤2:通过管道将JSON数据传递给解析命令(可以是另一个Python内联命令,也可以是定义好的函数)。 步骤3:在解析命令中提取所需值。 示例脚本: 示例1:解析单层JSON ```bash #!/bin/bash # 生成JSON的Python命令 json_data=$(python3 -c "import json; print(json.dumps({'name': 'Alice', 'age': 30}))") # 解析JSON的Python命令(直接) name=$(echo "$json_data" | python3 -c "import sys, json; print(json.load(sys.stdin)['name'])") echo "Name: $name" # 使用函数解析 function get_json_key() { python3 -c "import sys, json; print(json.load(sys.stdin).get('$1', ''))" } age=$(echo "$json_data" | get_json_key 'age') echo "Age: $age" ``` 示例2:解析多层JSON ```bash #!/bin/bash # 生成包含嵌套的JSON json_data=$(python3 -c "import json; print(json.dumps({'person': {'name': 'Alice', 'age': 30}}))") # 直接解析多层 name=$(echo "$json_data" | python3 -c "import sys, json; print(json.load(sys.stdin)['person']['name'])") echo "Name: $name" # 使用支持点分路径的函数 function get_json_value() { local key_path="$1" python3 -c "import sys, json; data = json.load(sys.stdin); keys = '$key_path'.split('.'); for key in keys: data = data[key]; print(data)" } # 注意:如果值可能包含换行,这个函数可能不适用(因为print(data)如果输出多行,会被Shell拆分成多行,然后赋值给变量时只取第一行?) # 所以,如果值可能有多行,需要确保输出是单行?或者用其他方法。 # 使用函数解析 person_name=$(echo "$json_data" | get_json_value "person.name") echo "Person Name: $person_name" person_age=$(echo "$json_data" | get_json_value "person.age") echo "Person Age: $person_age" ``` 注意:如果JSON值包含多行(如一个字符串内有换行符),那么使用print输出时,Shell在命令替换($(...))中会保留换行。但是,在变量赋值时,如果希望保留整个内容,需要适当处理(比如用双引号包裹变量)。但是,如果解析后的值本身包含多行,那么在使用时(比如echo)可能会显示多行。如果不需要多行,可以将其转换为单行(如用json.dumps转换?),但通常我们解析的是单个值(字符串、数字等),不会有多行。 但是,如果解析的是数组或对象,那么输出可能是一个JSON字符串,这时可能会有多行(取决于Python的json.dumps是否格式化)。为了避免多行,可以在打印时使用json.dumps(..., separators=(',',':'))来压缩成一行。 然而,在解析命令中,我们通常只获取一个简单的值(字符串、数字),所以直接打印即可。如果是复杂结构,建议在解析命令中输出为JSON字符串,然后在Shell中再解析(如果需要的话),或者直接使用jq。 另一个重要问题:特殊字符(如引号)可能会破坏命令。因此,如果JSON中包含引号等,需要确保Python代码正确处理。因为我们是使用json.load解析,所以不会出错(因为JSON是标准格式)。但是,在构造Python命令时,如果键中包含单引号,那么我们的函数中`'$1'`会出问题。所以,对于键中包含单引号的情况,需要转义。 改进:在函数中,将键作为参数传递给Python,而不是字符串替换。我们可以使用sys.argv[1]来传递键。 修改函数(单层键): ```bash function get_json_key() { python3 -c "import sys, json; data = json.load(sys.stdin); print(data.get(sys.argv[1], ''))" "$1" } ``` 这样,键通过命令行参数传递,避免了在Python代码中用字符串替换。 同样,多层键的函数也可以改为通过参数传递路径,然后在Python中拆分: ```bash function get_json_value() { python3 -c "import sys, json; data = json.load(sys.stdin); keys = sys.argv[1].split('.'); for key in keys: data = data[key]; print(data)" "$1" } ``` 这样更安全。 最后,关于错误处理:如果键不存在,上面的多层函数会抛出异常,导致Shell脚本出错。我们可以添加异常处理(返回空字符串或错误信息)。 改进(多层键,带异常处理): ```bash function get_json_value() { python3 -c "import sys, json, traceback; try: data = json.load(sys.stdin); keys = sys.argv[1].split('.'); for key in keys: data = data[key]; print(data) except Exception as e: # 可以选择打印空字符串,或者错误信息(但通常Shell脚本希望空) # print('') # 或者打印错误到标准错误,然后退出非0?但函数中不能退出整个脚本 sys.stderr.write('Error: '+str(e)+'\\n') sys.exit(1) " "$1" } ``` 在Shell中,如果解析失败,我们可以根据返回值(非0)来处理。 示例:如果键不存在,则函数返回非0,且打印错误到标准错误。 在Shell脚本中使用: value=$(echo "$json_data" | get_json_value "some.key") || exit 1 这样,如果解析失败,整个脚本退出。 综上所述,我们提供了两种方式: 1. 直接在Shell中写Python内联命令解析(适合简单、固定结构) 2. 定义函数(支持单层键和多层键)来灵活解析。 根据需求选择即可。 注意:如果JSON数据是由其他Python程序生成,确保生成时使用`json.dumps`输出,并且不要添加额外输出(如日志),否则解析会失败。 另外,如果生成JSON的Python命令输出多行,那么管道传递是完整的(JSON可以是多行的,因为json.load可以读取多行直到EOF)。所以,生成JSON时,可以格式化输出也可以压缩成一行,解析命令都能处理。 最后,提供一个完整的示例脚本,该脚本生成一个包含嵌套的JSON,然后解析多个字段: ```bash #!/bin/bash # 生成JSON的Python命令(多行JSON) json_data=$(python3 -c "import json; print(json.dumps({'person': {'name': 'Alice', 'age': 30, 'address': {'city': 'Beijing', 'postcode': '100000'}}}))") # 定义解析函数(支持多层键,带错误处理) function get_json_value() { python3 -c "import sys, json, traceback try: data = json.load(sys.stdin) keys = sys.argv[1].split('.') for key in keys: data = data[key] # 如果值是字符串、数字等,直接打印;如果是字典或列表,则打印JSON字符串 if isinstance(data, (dict, list)): print(json.dumps(data)) else: print(data) except Exception as e: sys.stderr.write('Error: {}\\n'.format(str(e))) sys.exit(1) " "$1" } # 解析各个字段 name=$(echo "$json_data" | get_json_value "person.name") || exit 1 age=$(echo "$json_data" | get_json_value "person.age") || exit 1 city=$(echo "$json_data" | get_json_value "person.address.city") || exit 1 postcode=$(echo "$json_data" | get_json_value "person.address.postcode") || exit 1 # 打印结果 echo "Name: $name" echo "Age: $age" echo "City: $city" echo "Postcode: $postcode" # 解析整个地址(是一个对象) address=$(echo "$json_data" | get_json_value "person.address") echo "Full Address: $address" # 这里address是一个JSON字符串 ``` 输出: Name: Alice Age: 30 City: Beijing Postcode: 100000 Full Address: {"city": "Beijing", "postcode": "100000"} 注意:函数中,如果解析的值是复杂类型(字典或列表),我们输出JSON字符串,这样在Shell中可以作为字符串传递,如果需要进一步解析,可以再次调用函数(或使用jq)。 这样,我们就实现了在Shell脚本中解析Python生成的JSON数据。 相关问题: 1. 如何安全地在Shell中处理包含特殊字符的JSON键名? 2. 除了使用Python,还有哪些工具可以在Shell中解析JSON(如jq)? 3. 如何处理JSON中可能缺失的键而不导致脚本退出? 4. 如何优化大量JSON数据的解析性能?</think>在Shell脚本中解析Python生成的JSON数据,可以通过管道将Python输出传递给解析命令实现。以下是三种高效方法: ### 方法1:直接管道解析(推荐) ```bash #!/bin/bash # Python生成JSON数据 python_output=$(python3 -c "import json; print(json.dumps({'name': 'Alice', 'age': 30, 'address': {'city': 'Paris'}}))") # 解析JSON字段 name=$(echo "$python_output" | python3 -c "import sys,json; print(json.load(sys.stdin)['name'])") age=$(echo "$python_output" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data['age'])") city=$(echo "$python_output" | python3 -c "import sys,json; print(json.load(sys.stdin)['address']['city'])") echo "Name: $name" echo "Age: $age" echo "City: $city" ``` ### 方法2:使用可复用函数(支持嵌套键) ```bash #!/bin/bash # JSON解析函数(支持点分隔嵌套键) get_json_value() { python3 -c "import sys,json; data = json.load(sys.stdin); keys = '$1'.split('.'); for key in keys: data = data[key]; print(data)" } # Python生成JSON数据 json_data=$(python3 -c "import json; print(json.dumps({'user': {'name': 'Bob', 'scores': [90,85,95]}}))") # 解析嵌套字段 name=$(echo "$json_data" | get_json_value "user.name") first_score=$(echo "$json_data" | get_json_value "user.scores.0") echo "User: $name" echo "First Score: $first_score" ``` ### 方法3:处理多行JSON(含数组) ```bash #!/bin/bash # Python生成多行JSON数组 python3 -c "import json data = [{'id':1,'name':'Alice'},{'id':2,'name':'Bob'}] print(json.dumps(data))" > tmp.json # 逐项解析JSON数组 while IFS= read -r line; do id=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") name=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['name'])") echo "ID: $id, Name: $name" done < <(python3 -c "import json; [print(json.dumps(item)) for item in json.load(open('tmp.json'))]") # 清理临时文件 rm tmp.json ``` ### 关键要点说明 1. **数据传递原理** 通过管道`|`或进程替换`<(...)`将Python输出作为标准输入(stdin)传递给解析命令[^1] 2. **嵌套字段处理** 使用点分隔语法`key1.key2`访问嵌套字段,如`data['address']['city']`简化为`address.city`[^2] 3. **错误处理技巧** 添加异常捕获避免脚本中断: ```python try: print(data['key']) except KeyError: print('') ``` 4. **编码问题预防** 中文等特殊字符需声明编码: ```bash export PYTHONIOENCODING=utf8 ``` 5. **性能优化** 对于大数据集: - 使用`jq`替代Python解析(需安装) - 减少子进程调用次数 ```bash # 使用jq解析示例 echo "$json_data" | jq '.user.name' ``` > **操作演示** > ```bash > # 生成测试数据 > $ python3 -c "import json; print(json.dumps({'temp': 25.6}))" > {"temp": 25.6} > > # 解析温度值 > $ echo '{"temp": 25.6}' | python3 -c "import sys,json; print(json.load(sys.stdin)['temp'])" > 25.6 > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值