如何使bash脚本从命令行(位置),stdout(管道)和进程替换中获取输入?

时间:2011-07-09 22:09:26

标签: bash unix shell scripting

我写了一个脚本,它带有一堆选项(-d,-v,-l,以及--version, - leader等等),然后其余的文本($ *)可以是任何东西。该脚本处理文本并将其重新格式化。它很长,所以这是一个浓缩版本:


## ------------------------- [myScript.sh] --------------- ----------- ##

(1)设置默认值:

declare -- v='1.0' FS=$':\n\r\v\f\t'  Application='Finder' Files s='s'
declare -i errors=0 element=0 counter=0 n L=2

(2)解析用户输入:

until [[ -z "$1" || "$1" == '--' || "${1:0:1}" != '-' ]]; do
    [ "$Input" ] && unset Input
    if [[ "$1" =~ ^(-[Ww]|--[Ww]idth=)([0-9]+)? ]]; then
        Input=$(echo "$1" | gsed -re 's|--?W(idth=)?||I' | grep -Eoe '^(0|[1-9][0-9]*)$')
        [ -z "$Input" ] && echo "$2" | grep -Eoe '^(0|[1-9][0-9]*)$' && Input="$2" && shift 1
        (( Input >= 0 )) && Width="$Input" || unset Width
    elif [[ "$1" =~ ^((-[LlIi]|--(([Ll]ead(er|ing)?)?([Ii]n(dent)?)|[Ll]ead(er|ing)?)=)([0-9]+)?)$ ]]; then
        Input=$(echo "$1" | gsed -re 's|--?[a-z]+=?||I' | grep -Eoe '^(0|[1-9][0-9]*)$')
        [ -z "$Input" ] && echo "$2" | grep -Eoe '^(0|[1-9][0-9]*)$' && Input="$2" && shift 1
        (( Input >= 0 )) && L="$Input" || unset L
    ...
    else printf "$(Bold 'Error:') Unrecognized option: '$(Tbrown "$1")'\n\n" >&2
        exit 2
    fi
    shift 1
done

(3) 现在在这里,获取文字:

IFS=''
[ -n "${*}" ] && declare Text="${*}" || Text="$(cat)" ## could also use read instead of cat ##
[ -z "$Text" ] && printf "$(Bold 'Error:') No text entered...\n\n" >&2 && exit 2

(4)处理文本:

Text="$(echo "$Text" | gsed -rne '1h;1!H;$g;s|[\x0A-\x0D]+| |g;$p' | expand -t4 )"
echo "$Text"    ##  (temporary)   ##
exit 0          ##  (temporary)   ##
...             ## (process text) ##
...             ## (process more) ##

第3部分用于接受在选项之后和管道输入时输入的文本,但是如果没有输入文本则挂起等待输入,并且看不到作为过程替换传递的文本...例如:

示例:

./myScript.sh -L10 --width=20 'This is a test'
> This is a test

echo 'This is a test' | ./myScript.sh -L10 --width=20
> This is a test

./myScript.sh -L10 --width=20 < <( echo 'This is a test' )
> This is a test

./myScript.sh -L10 --width=20      ##* Want to stop this *##
> (No output)... hangs waiting on cat (or read) for a ^D

echo 'This is a test' >( ./myScript.sh )
> This is a test /dev/fd/63
> <B>Error:</B> No text entered...

./myScript.sh -L10 --width=20 <<<'This is a test'
> This is a test

echo "This is a test" | tee >( ./myScript.sh -L10 --width=20 ) >( ./myScript.sh  )
> This is a test
> This is a test
> This is a test

如何让脚本不挂猫或读取,等待输入? (没有使用超时或读取-t因为只会减慢速度)?

2 个答案:

答案 0 :(得分:1)

我认为你想要的是避免从终端阅读:

if test -t 0; then
    echo "Not reading from terminal.  Pipe through cat if you really want to do this" >&2
    exit 1
fi

无法检测到没有专门为程序提供任何输入源的一般情况;所有重定向都由shell完成,你的程序无法判断它的标准输入是专门给它还是从shell继承。

答案 1 :(得分:0)

搞定了!谢谢geekosaur!在您的帮助下,这是我的解决方案:

IFS='' if [ -n "${*}" ]; then declare Text="${*}" elif [ -t 0 ]; then TempT="$(mktemp -t 'Entered Text')" printf "\n$(Bold Enter text here:)\n\t** Note - To $(Tred stop) reading from stdin, enter $(Tred '/EOT')\n$(Twhite '∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞')\n" counter=0 until [[ "$line" == /[Ee][0Oo][Tt] ]]; do (( counter++ )) read -rp "$(printf "$(Twhite '⋇')$(Taqua "%%7s")$(Twhite '⋇ ')" "$(frame 0 7 "[$counter]" 0 - --no)")" -e line echo "$line" >>"$TempT" done readarray -t EnteredtText <"$TempT" ( set -u; [[ -f "$TempT" && "$TempT" =~ ^(/private)?/tmp/ ]] && { mv -f "$TempT" "$HOME/.Trash/" || rm -f "$TempT"; } ) &>'/dev/null' IFS=$'\n' declare Text="$(echo "${EnteredtText[*]}" | gsed -e '$d')" else Text="$(cat)" fi
在这个问题上,我有另一个问题:

是否有可能(我确定是)在不使用文件的情况下从stdin读取文本块?

那个讨厌的while循环运行一个子shell。我试了几件事:
IFS=''
if [ -n "${*}" ]; then
    declare Text="${*}"
elif [ -t 0 ]; then
    TempT="$(mktemp -t 'Entered Text')"
    printf "\n$(Bold Enter text here:)\n\t** Note - To $(Tred stop) reading from stdin, enter $(Tred '/EOT')\n$(Twhite '∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞∞')\n"
    counter=0
    until [[ "$line" == /[Ee][0Oo][Tt] ]]; do
        (( counter++ ))
        read -rp "$(printf "$(Twhite '⋇')$(Taqua "%%7s")$(Twhite '⋇ ')" "$(frame 0 7 "[$counter]" 0 - --no)")" -e line
        echo "$line" >>"$TempT"
    done
    readarray -t EnteredtText <"$TempT"
    ( set -u; [[ -f "$TempT" && "$TempT" =~ ^(/private)?/tmp/ ]] && { mv -f "$TempT" "$HOME/.Trash/" || rm -f "$TempT"; } ) &>'/dev/null'
    IFS=$'\n'
    declare Text="$(echo "${EnteredtText[*]}" | gsed -e '$d')"
else Text="$(cat)"
fi

exec 7<&0 exec 0<Temp ## Perhaps some process sub? ## until [[ "$line" == '/EOT' ]]; do read line done >Temp ## or sub here? ## exec 0<&7 exec 7<&- mapfile Text <Temp

exec 7<&0
exec 0<Temp ## Perhaps some process sub? ##
until [[ "$line" == '/EOT' ]]; do
    read line
done >Temp  ## or sub here? ##
exec 0<&7
exec 7<&-
mapfile Text <Temp
也许这里有一个 -
while read line; do [[ "$line" != '/EOT' ]] && readarray Text <( echo "$line" ) done
- 类型的命令?我已经编写了不到2年的脚本,对于我的生活,我无法理解I / O重定向(超出基础)。在许多不同的书籍/表格中重新阅读这一章总是导致大量的测试,我没有得到它[我想你看到了;]],我感到沮丧,最后我找到另一种方式来做我的事情想。除了0/1/2之外,那个神秘的命令while read line; do [[ "$line" != '/EOT' ]] && readarray Text <( echo "$line" ) done和所有文件desriptors都没有在我的任何脚本中使用......但是我知道有一种方法可以在没有文件创建和后续删除的情况下回答这个问题。 p>

谢谢!