将输出导入到Bash函数

102

我在一个Bash脚本中有一个简单的函数,我想将标准输出作为其输入进行管道操作。

jc_hms(){
  printf "$1"
}

我想以这种方式使用它。

var=`echo "teststring" | jc_hms`
当然,我使用了冗余的echo和printf函数来简化问题,但你明白我的意思。现在我得到一个“not found”错误,我认为这意味着我的参数分隔有误("$1"部分)。有什么建议吗?
最初,jc_hms函数是这样使用的:
echo `jc_hms "teststring"` > //dev/tts/0

但是我想先将结果存储到一个变量中以便进一步处理,然后再发送到串行端口。

编辑: 所以要澄清的是,我不是试图将东西打印到串行端口,我想要使用"|"管道字符与我的bash函数进行接口,我想知道这是否可能。

编辑:好的,这是完整的函数。

jc_hms(){
  hr=$(($1 / 3600))
  min=$(($1 / 60))
  sec=$(($1 % 60))

  printf "$hs:%02d:%02d" $min $sec
}

我正在使用该函数来构建一个字符串,它来自以下这行代码

songplaytime=`echo $songtime | awk '{print S1 }'`
printstring="`jc_hms $songplaytime`"  #store resulting string in printstring

$songtime是一个由空格分隔的字符串,格式为"playtime totaltime"。

我希望能够在一行中完成这个操作,并将其管道传递给awk。

printstring=`echo $songtime | awk '{print S1 }' | jc_hms`

就像这样。


3
你的问题在于"$1"是函数的命令行参数,而不是标准输入,管道中的文本才是标准输入中的内容。我会尽力使翻译更加通俗易懂但不改变原意。 - chepner
1
那么我该如何访问标准输入?例如? - jliu83
2
我认为这可能是一个XY问题。你能否更新你的问题,告诉我们你真正想要实现什么,而不是你如何尝试去实现它? - ghoti
jliu83:如果jc_hms在管道的接收端,那么标准输入将被呈现给函数内部的第一个命令。但是,请发布jc_hms的真实样貌,以便我们确定您情况下的最佳解决方案。 - chepner
请注意,您的函数有误。它应该像这样:jc_hms() { hr=$(($1 / 3600)); min=$((($1 % 3600) / 60)); sec=$((1 % 60)); printf "$hs:%02d:%02d" $min $sec; } - Jo So
9个回答

92

针对您的实际问题,当一个shell函数处于管道的接收端时,该函数中的所有命令都会继承标准输入流,但只有实际读取标准输入数据的命令会消耗数据。对于依次运行的命令,后续命令只能看到之前未被消耗的数据。当两个命令并行运行时,哪些命令看到哪些数据取决于操作系统如何调度这些命令。

由于printf是您的函数中的第一个且唯一的命令,因此实际上忽略了标准输入流。有几种解决方法,包括使用 read 将标准输入读入变量中,然后将该变量传递给printf等方法:

jc_hms () {
    read foo
    hr=$(($foo / 3600))
    min=$(($foo / 60))
    sec=$(($foo % 60))
    printf "%d:%02d:%02d" "$hr" "$min" "$sec"
}

然而,由于您似乎需要使用 awk,才需要一个管道,那么让我提出以下替代方案:

printstring=$( jc_hms $songtime )

由于songtime由一对用空格分隔的数字组成,因此Shell会对songtime的值执行单词拆分,并且jc_hms会看到两个独立的参数。这不需要更改jc_hms的定义,也不需要通过标准输入将任何内容传递给它。

如果您仍然有其他原因让jc_hms读取标准输入,请告诉我们。


我遇到一个与 read 相关的问题,可以尝试这个命令:echo -e "\ta" |if read str;then echo "$str";fi,它将忽略制表符并只打印 "a",有什么提示吗? - Aquarius Power
1
在这里找到解决方法:https://dev59.com/fGw05IYBdhLWcg3wUAM0 - Aquarius Power
“echo” 比 “printf” 更简洁,是吗? - Sandburg
3
“echo”从未完全标准化,因为已经存在太多不同的(有时是矛盾的)实现。例如,POSIX标准要求“echo”处理像“\t”和“\n”这样的序列,但是“bash”的实现除非使用非标准选项“-e”,否则不会这样做。相比之下,“printf”更具可移植性。 - chepner
1
“标准输入是由函数内执行的第一个命令读取的”——这并不正确。实际上,任何从stdin读取的命令都会这样做,假设在它之前没有任何命令已经消耗了流。你可以有500行的“printf”,然后在函数的最后一行使用cat,函数将打印出你的500行,然后cat将读取stdin的内容并将其写入stdout。 - JakeRobb
@JakeRobb 同意,答案措辞不当。 - chepner

73

你不能直接将内容管道传递给bash函数,但是你可以使用read命令将其输入:

jc_hms() {
  while read -r data; do
      printf "%s" "$data"
  done
}

应该是你想要的


1
你能展示一下如何使用它的例子吗?我还是得以同样的方式使用它吗? - jliu83
1
你可以使用这个或者我的答案,就像你在问题中所指示的那样。 - chepner
如果我按照你的建议将printf替换为echo "manipulated $data",并从命令行运行var=$(echo "teststring" | jc_hms); echo $var,那么我会得到"manipulated teststring"。已编辑为$(..),因为反引号在注释中不会显示,但是你原来的赋值应该可以工作。 - moopet
这对我来说就可以了。有没有办法用一行代码读取所有的数据? - Avindra Goolcharan
5
应该写成 printf "%s" "$data",否则 $data 将被解释为格式化字符串。 - Raphael Ahrens

71

1)我知道这是一个相当旧的帖子。

2)我喜欢这里大多数的答案。

不过,我发现这篇文章是因为我需要类似的东西。虽然大家都同意需要使用stdin,但这里的答案缺少/dev/stdin文件的实际用法。

使用read内置函数会强制将此函数与管道输入一起使用,因此不能以典型的方式使用它。我认为利用/dev/stdin是解决这个问题的一种更好的方法,因此我想为完整性添加我的意见。

我的解决方案:

jc_hms() { 
  declare -i i=${1:-$(</dev/stdin)};
  declare hr=$(($i/3600)) min=$(($i/60%60)) sec=$(($i%60));
  printf "%02d:%02d:%02d\n" $hr $min $sec;
}

实践中:

user@hostname:pwd$ jc_hms 7800
02:10:00
user@hostname:pwd$ echo 7800 | jc_hms 
02:10:00

我希望这能对某人有所帮助。

愉快的编程!


3
非常有用 - 我正需要这个功能,能够在函数中处理参数或标准输入。 - darrend
非常好!但似乎是一个bashism(在BASH下工作,但不适用于DASH)。我把它放在一个文件中:#!/bin/sh jc_hms() { i="${1:-$(然后chmod +x test.sh ./test.sh 02:10:00 ./test.sh: 6: ./test.sh: arithmetic expression: expecting primary: "/3600" - gildux
4
绝对棒,谢谢!在已安装bash 4.4的MacOS上,我无法使用declare -i,但我只需完成我的任务:i=${1:-$(</dev/stdin)}; - ptim
唯一的问题是,由于它使用${1},如果直接调用,则不接受多个参数。例如,jc_hms 7800 4321将完全忽略4321输入,而echo 7800 4321 | jc_hms则按预期工作。 - ijoseph
@ijoseph 我同意,这是为了支持单个参数而设计的。鉴于原始问题,我不确定为什么会有多个参数,但是,您只需要一个简单的for循环包装器即可完成此操作:for x; do jc_hms "$x"; done - user.friendly
显示剩余2条评论

30

或者,你也可以用简单的方式来实现。

jc_hms() {
    cat
}

虽然迄今为止所有的回答都忽略了这一事实,即这不是 OP 想要的(他说这个函数是简化的)


谢谢这个,现在我知道我可以将其用于管道传输到printf中,例如xdotool search --onlyvisible --name 'Audacity' | printf "0x%08x\n" `cat` - sdaau
1
你可以使用以下方法,参考我的下面的答案:`# echo 12345-1234 | printf 'Zip: %s\n' $( - user.friendly
@TrueFalse:是的,这样做可以实现效果,但它使用了shell来将stdin转换回作为printf参数的参数。printf从不读取stdin,所以将其作为管道输入是没有意义的。(你的操作和printf 'Number: %d\n' $(echo 1 2 3 4)是一样的) - Jo So
@Jo So:这是真的,OP的问题也可以这样说。在这里使用它是毫无意义的,但它展示了这种方法。你说你不能将管道传输到printf中;这并不完全正确,所以我只是为那些可能希望将管道传输到printf中的人提出了一个论点。 - user.friendly
1
干得好。如果您想将其管道传递到后续命令,则此方法适用于各种事物。很好知道。 - JJ Ward
显示剩余2条评论

22

我喜欢用户friendy使用的Bash内置条件取消替换语法的答案。这是对他的答案进行轻微调整,使其更加通用,适用于具有不确定参数计数的情况:

function myfunc() {
    declare MY_INPUT=${*:-$(</dev/stdin)}
    for PARAM in $MY_INPUT; do
        # do what needs to be done on each input value
    done
}

你能解释一下 ${*:-$(</dev/stdin)} 的确切工作原理吗? - user56834
1
@user56834 $1或${1}是第一个位置参数。$或${}(也可以用$@)代表所有位置参数。在Bash参数替换中,:-设置了默认值以防您访问的参数没有值。 $( )是命令替换,会执行括号里的任何内容。 <cat的简写,它读取文件内容。 在这种情况下,文件是/dev/stdin。 - user.friendly

1

看起来似乎什么都不起作用,但是有一些变通方法。

提到了xargs ref函数的解决方法

$ FUNCS=$(functions hi); seq 3 | xargs -I{} zsh -c "eval $FUNCS; hi {}"

如果您的函数可能引用另一个函数,那么这也无法正常工作。因此,我最终编写了一些接受管道输入的函数,就像这样:

somefunc() {
    while read -r data; do
        printf "%s" "$data"
    done
}

1

嗯......

songplaytime=`echo $songtime | awk '{print S1 }'`
printstring="`jc_hms $songplaytime`"  #store resulting string in printstring

如果你已经在调用awk,为什么不使用它呢?
printstring=`TZ=UTC gawk -vT=$songplaytime 'BEGIN{print strftime("%T",T)}'`

我假设您正在使用Gnu的Awk,这是最好的也是免费的; 这将适用于常见的Linux发行版,这些发行版不一定使用最新的gawk。最新版本的gawk将允许您将UTC指定为strftime()函数的第三个参数。

1
所提出的解决方案需要有关stdin或read的内容仅在特定条件下被调用。否则,函数将等待来自控制台的内容,并在继续之前需要按Enter或Ctrl + D。
解决方法是使用带有超时的read。例如:read -t <秒数>
function test ()
{
  # ...
  # process any parameters
  # ...
  read -t 0.001 piped
  if [[ "${piped:-}" ]]; then
    echo $piped
  fi
}

注意,对我来说-t 0没有起作用。
您可能需要使用不同的超时值。 过小的值可能会导致错误,而过大的超时会延迟脚本。

一个更好的测试空标准输入的方法似乎是 [[ ! -t 0 ]],就像这个答案中所示:https://unix.stackexchange.com/a/388462/84777 - mattmc3

0
我知道这是一个旧帖子,但当我在搜索引擎上寻找正确的方法时,它是搜索结果中的第一个。
我更喜欢这种方式,因为它可以涵盖所有情况:
(a)通过函数内的参数$1接收数据
(b)通过管道接收数据
(c)避免挂起,如果/dev/stdin未打开-没有数据在管道中或没有给定参数。
function somefunc() {
if test -n "$1"; then
   dt="$1"
   #Read from positional argument $1;
elif test ! -t 0; then
   dt="$(</dev/stdin)"
   #Read from stdin if file descriptor /dev/stdin is open
else
   echo "No standard input."
fi
}

来源:https://www.baeldung.com/linux/pipe-output-to-function

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接