【问题标题】:How do I use `sed` to alter a variable in a bash script?如何使用 `sed` 更改 bash 脚本中的变量?
【发布时间】:2017-04-03 13:34:32
【问题描述】:

我正在尝试使用 enscript 从 Mutt 打印 PDF,但遇到了字符编码问题。解决它们的一种方法似乎是只使用 sed 替换问题字符:sed -ir 's/[“”]/"/g' {input}

我的测试输入文件是这样的:

“very dirty”    
we’re 

我希望得到"very dirty"we're,但我还是得到了

â\200\234very dirtyâ\200\235
weâ\200\231re

我在printing to PDFs from Mutt 上找到了一个不错的小帖子,我以此为起点。我有一个 bash 脚本,我从我的 .muttrcset print_command="$HOME/.mutt/print.sh" 指向——该脚本目前的内容如下:

#!/bin/bash
input="$1" pdir="$HOME/Desktop" open_pdf=evince


# Straighten out curly quotes

sed -ir 's/[“”]/"/g' $input
sed -ir "s/[’]/'/g" $input


tmpfile="`mktemp $pdir/mutt_XXXXXXXX.pdf`"
enscript --font=Courier8 $input -2r --word-wrap --fancy-header=mutt -p - 2>/dev/null | ps2pdf - $tmpfile
$open_pdf $tmpfile >/dev/null 2>&1 &
sleep 1
rm $tmpfile

它在创建 PDF 方面做得很好(如果你给它一个文件作为参数,它工作得很好)但我不知道如何修复花引号。

我在sed 行上尝试了很多变体:

input=sed -r 's/[“”]/"/g' $input

$input=sed -ir "s/[’]/'/g" $input

根据Can I use sed to manipulate a variable in bash? 的建议,我也尝试了input=$(sed -r 's/[“”]/"/g' <<< $input),但出现错误:“语法错误:重定向意外”

但是没有人能够真正改变$input——用sed改变$input的正确语法是什么?

注意:我接受了一个解决了我提出的问题的答案,但正如您从 cmets 看到的,这里还有几个其他问题。 enscript 将整个文件作为变量,而不仅仅是文件的文本。因此,尝试调整文件中的文本将需要一些额外的步骤。我还在学习。

【问题讨论】:

  • 也许你需要设置你的语言环境stackoverflow.com/questions/27072558/sed-and-utf-8-encoding
  • 请注意,如果您希望这些引号实际影响参数的分组方式(即具有语义意义),那么在您的脚本启动后修复它为时已晚——它们已经被视为文字而不是句法。
  • 另外,考虑使用小写的变量名——根据环境变量名的 POSIX 规范,全大写的名称保留给修改系统或 shell 行为的变量,而名称至少保留一个小写字符供应用程序使用。由于设置一个名称与环境变量重叠的 shell 变量会覆盖后者,因此这些约定必然适用于这两个地方。
  • ...你也可以考虑通过shellcheck.net运行你的代码
  • @Stargateur 我将进行编辑,但它在命令行上运行良好。它只是没有改变

标签: bash sed


【解决方案1】:

关于编辑变量的一般性

BashFAQ #21 是关于在 bash 中执行搜索和替换操作的综合参考,包括在变量中,因此推荐阅读。在这种特殊情况下:

改用 shell 的原生字符串操作;这比分叉子shell、在其中启动一个外部进程并读取该外部进程的输出要高得多。 BashFAQ #100 详细介绍了这个话题,值得一读。

根据您的 bash 版本和配置的语言环境,可能可以使用括号表达式(即 [“”],就像您的原始代码一样)。但是,最便携的方法是将 分开处理,即使没有可用的多字节字符支持也可以工作。

input='“hello ’cruel’ world”'
input=${input//'“'/'"'}
input=${input//'”'/'"'}
input=${input//'’'/"'"}
printf '%s\n' "$input"

...正确输出:

"hello 'cruel' world"

关于使用sed

为了提供一个字面上的答案——你几乎在你的问题中有一个基于sed的有效方法。

input=$(sed -r 's/[“”]/"/g' <<<"$input")

...在$input 的参数扩展周围添加缺少的句法双引号,确保它被视为单个标记,无论它是如何进行字符串拆分或全局扩展。


但这一切都无济于事......

之所以提到以下内容,是因为您的测试脚本正在操作通过命令行传递的内容;如果生产中不是这种情况,您可以忽略以下内容。

如果您的脚本以./yourscript “hello * ’cruel’ * world” 调用,那么在脚本启动之前用户输入的确切信息会丢失,您在此处无法解决此问题

这是因为$1,在那种情况下,将只包含“hello’cruel’world” 位于它们自己的 argv 位置,并且在脚本启动之前,*s 将被当前目录中的文件列表替换(每个这样的文件都被替换为单独的参数)。因为负责解析用户命令行的 shell(它不是运行脚本的同一个 shell!)在运行此解析时没有将引号识别为有效,所以在脚本运行时,您将无能为力恢复原始数据。

【讨论】:

  • @sorontar,当然——但是如果标题询问如何修改 变量,这就是我要回答的问题,因为其他任何人都想知道如何修改变量将看到该标题并提出问题并期望它会有所帮助。
  • ...也就是说,你有一个很好的答案,我很高兴他们都在这里。
【解决方案2】:

摘要:探索了使用 sed 更改变量的方法,但您真正需要的是一种使用和编辑文件的方法。前面已经覆盖了。

Sed

(两个)sed 行可以用这个来解决(注意 -i 没有被使用,它不是一个文件而是一个值):

input='“very dirty”    
we’re'

sed 's/[“”]/\"/g;s/’/'\''/g' <<<"$input"

但是使用 shell 的内部应该更快(对于小字符串):

input='“very dirty”    
we’re'

input=${input//[“”]/\"}
input=${input//[’]/\'}
printf '%s\n' "$input"

$1

但是您的脚本存在潜在问题,您正在尝试清理从命令行接收的输入。您正在使用 $1 作为字符串的来源。一旦有人写:

./script  “very dirty”    
we’re

该输入丢失。它被分解为 shell 的令牌,“$1”将仅是 “very

但我不相信那是你真正拥有的。

文件

但是,您也说输入来自文件。如果是这样,请阅读:

input="$(<infile)"           # not $1

sed 's/[“”]/\"/g;s/’/'\''/g' <<<"$input"

或者,如果您不介意编辑(更改)文件,请改为:

sed -i 's/[“”]/\"/g;s/’/'\''/g' infile
input="$(<infile)"

或者,如果您清楚并确定给脚本的内容是文件名,例如:

./script infile

你可以使用:

infile="$1"
sed -i 's/[“”]/\"/g;s/’/'\''/g' "$infile"
input="$(<"$infile")"

其他cmets:

然后:

  • 引用您的变量。
  • 不要使用非常古老的`…` 语法,而是使用$(…)
  • 请勿使用大写的变量,这些变量是为环境变量保留的。
  • 并且(除非您实际上是指 sh)使用针对 bash 的 shebang(第一行)。
  • enscript 命令最明确地需要一个文件,而不是一个变量。
  • 也许你应该使用evince打开PS文件,不需要制作pdf的步骤,除非你知道你真的需要它。
  • 我认为最好使用文件来存储 enscript 和 ps2pdf 的输出。
  • 在一切正常之前不要隐藏命令打印的错误,然后,只需将脚本调用为:

    ./script infile 2>/dev/null

    或根据需要使其不那么冗长。

最终脚本。

如果您使用 enscript 将要使用的文件的名称调用脚本,例如:

./script infile

然后,整个脚本将如下所示(在 bash 或 sh 中运行):

#!/usr/bin/env bash
Usage(){ echo "$0; This script require a source file"; exit 1; }
[ $# -lt 1 ] && Usage
[ ! -e $1 ] && Usage
infile="$1"
pdir="$HOME/Desktop"
open_pdf=evince

# Straighten out curly quotes
sed -i 's/[“”]/\"/g;s/’/'\''/g' "$infile"

tmpfile="$(mktemp "$pdir"/mutt_XXXXXXXX.pdf)"
outfile="${tmpfile%.*}.ps"
enscript --font=Courier10 "$infile" -2r \
     --word-wrap --fancy-header=mutt -p "$outfile"

ps2pdf "$outfile" "$tmpfile"

"$open_pdf" "$tmpfile" >/dev/null 2>&1 &
sleep 5
rm "$tmpfile" "$outfile"

【讨论】:

  • 我想你的意思是$#,而不是#@
  • @CharlesDuffy Doh!是的,这就是我的意思,编辑,谢谢。
  • 有趣。如果我按照您的建议从命令行运行它,这可以正常工作,但是当我从 Mutt 运行它时它不起作用。 Mutt 说“无法打印消息”
  • @Amanda 抱歉,“在 Mutt 工作”是一个全新的问题。请问一下。
猜你喜欢
  • 2014-01-09
  • 2011-03-13
  • 2014-11-01
  • 1970-01-01
  • 2014-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多