首页 文章

如何读取Bash中的文件或stdin?

提问于
浏览
188

在Perl中,以下代码将从命令行args或stdin中指定的文件中读取:

while (<>) {
   print($_);
}

这很方便 . 我只是想知道在bash中从file或stdin读取的最简单方法是什么 .

14 回答

  • 55

    如果使用文件名调用脚本作为第一个参数 $1 ,则从文件读取以下解决方案,否则从标准输入调用 .

    while read line
    do
      echo "$line"
    done < "${1:-/dev/stdin}"
    

    如果定义了替换 ${1:-...} ,则取代 $1 否则使用自己进程的标准输入的文件名 .

  • 13

    也许最简单的解决方案是使用合并重定向运算符重定向stdin:

    #!/bin/bash
    less <&0
    

    Stdin是文件描述符零 . 上面将输入管道传输给你的bash脚本到less的stdin .

    Read more about file descriptor redirection .

  • 84

    这是最简单的方法:

    #!/bin/sh
    cat -
    

    用法:

    $ echo test | sh my_script.sh
    test
    

    要将stdin分配给变量,您可以使用: STDIN=$(cat -) 或只是 STDIN=$(cat) 因为不需要运算符(根据@mklement0 comment) .


    要从标准输入中解析每一行,请尝试以下脚本:

    #!/bin/bash
    while IFS= read -r line; do
      printf '%s\n' "$line"
    done
    

    要从文件或stdin中读取(如果参数不存在),可以将其扩展为:

    #!/bin/bash
    file=${1--} # POSIX-compliant; ${1:--} can be used either.
    while IFS= read -r line; do
      printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
    done < <(cat -- "$file")
    

    注意事项: - read -r - 不要以任何特殊方式处理反斜杠字符 . 将每个反斜杠视为输入行的一部分 . - 如果不设置IFS,默认情况下,行的开头和结尾处的Space和Tab序列将被忽略(修剪) . - 当行包含单个-e,-n或-E时,使用printf而不是echo来避免打印空行 . 但是有一种解决方法是使用env POSIXLY_CORRECT = 1 echo“$ line”来执行支持它的外部GNU echo . 请参阅:如何回显“-e”?

    请参阅:stackoverflow SE上的How to read stdin when no arguments are passed?

  • 6

    只要 IFS 中断输入流, echo 解决方案就会添加新行 . @fgm's answer可以修改一下:

    cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
    
  • 0

    我认为这是直截了当的方式:

    $ cat reader.sh
    #!/bin/bash
    while read line; do
      echo "reading: ${line}"
    done < /dev/stdin
    
    $ cat writer.sh
    #!/bin/bash
    for i in {0..5}; do
      echo "line ${i}"
    done
    
    $ ./writer.sh | ./reader.sh
    reading: line 0
    reading: line 1
    reading: line 2
    reading: line 3
    reading: line 4
    reading: line 5
    
  • 2

    问题中的Perl循环从命令行上的所有文件名参数读取,如果没有指定文件,则从标准输入读取 . 如果没有指定文件,我看到的答案似乎都处理单个文件或标准输入 .

    虽然经常被嘲笑为UUOC(无用的 cat ),但有时 cat 是这项工作的最佳工具,可以说这是其中之一:

    cat "$@" |
    while read -r line
    do
        echo "$line"
    done
    

    唯一的缺点是它创建了一个在子shell中运行的管道,因此在管道外部无法访问 while 循环中的变量赋值 . bash 方式是Process Substitution

    while read -r line
    do
        echo "$line"
    done < <(cat "$@")
    

    这使得 while 循环在主shell中运行,因此循环中设置的变量可以在循环外部访问 .

  • 4

    Perl的行为,OP中给出的代码可以不带或多个参数,如果参数是单个连字符 - ,这被理解为stdin . 此外,始终可以使用 $ARGV 的文件名 . 迄今为止给出的答案都没有真正模仿Perl _1276132的纯粹Bash可能性 . 诀窍是适当使用 exec .

    #!/bin/bash
    
    (($#)) || set -- -
    while (($#)); do
       { [[ $1 = - ]] || exec < "$1"; } &&
       while read -r; do
          printf '%s\n' "$REPLY"
       done
       shift
    done
    

    文件名可在 $1 中找到 .

    如果没有给出参数,我们人为地将 - 设置为第一个位置参数 . 然后我们循环参数 . 如果参数不是 - ,我们将文件名的标准输入重定向到 exec . 如果此重定向成功,我们循环 while 循环 . 我正在使用标准 REPLY 变量,在这种情况下,您不需要重置 IFS . 如果你想要另一个名字,你必须像这样重置 IFS (当然,除非你不这样做):

    while IFS= read -r line; do
        printf '%s\n' "$line"
    done
    
  • 2

    更精确地...

    while IFS= read -r line ; do
        printf "%s\n" "$line"
    done < file
    
  • 12

    请尝试以下代码:

    while IFS= read -r line; do
        echo "$line"
    done < file
    
  • 0

    代码 ${1:-/dev/stdin} 只会理解第一个参数,那么,这个怎么样 .

    ARGS='$*'
    if [ -z "$*" ]; then
      ARGS='-'
    fi
    eval "cat -- $ARGS" | while read line
    do
       echo "$line"
    done
    
  • -1

    以下使用标准 sh (在Debian上使用_1276150测试)并且非常易读,但这是一个品味问题:

    if [ -n "$1" ]; then
        cat "$1"
    else
        cat
    fi | commands_and_transformations
    

    详细信息:如果第一个参数非空,则 cat 表示该文件,否则为 cat 标准输入 . 然后整个 if 语句的输出由 commands_and_transformations 处理 .

  • 311

    我发现这些答案都不可接受 . 特别是,接受的答案仅处理第一个命令行参数并忽略其余部分 . 它试图模拟的Perl程序处理所有命令行参数 . 所以接受的答案甚至没有回答这个问题 . 其他答案使用bash扩展,添加不必要的'cat'命令,仅适用于回输输入到输出的简单情况,或者只是不必要的复杂 .

    但是,我必须给他们一些信任,因为他们给了我一些想法 . 这是完整的答案:

    #!/bin/sh
    
    if [ $# = 0 ]
    then
            DEFAULT_INPUT_FILE=/dev/stdin
    else
            DEFAULT_INPUT_FILE=
    fi
    
    # Iterates over all parameters or /dev/stdin
    for FILE in "$@" $DEFAULT_INPUT_FILE
    do
            while IFS= read -r LINE
            do
                    # Do whatever you want with LINE here.
                    echo $LINE
            done < "$FILE"
    done
    
  • 1

    我结合了上述所有答案并创建了一个适合我需求的shell函数 . 这是我2台Windows10机器的cygwin终端,我在它们之间有一个共享文件夹 . 我需要能够处理以下事项:

    • cat file.cpp | tx

    • tx < file.cpp

    • tx file.cpp

    如果指定了特定的文件名,我需要在复制期间使用相同的文件名 . 在输入数据流通过管道传输的情况下,我需要生成具有小时分和秒的临时文件名 . 共享主文件夹具有一周中的几天的子文件夹 . 这是出于组织目的 .

    看哪,这是我需要的终极剧本:

    tx ()
    {
      if [ $# -eq 0 ]; then
        local TMP=/tmp/tx.$(date +'%H%M%S')
        while IFS= read -r line; do
            echo "$line"
        done < /dev/stdin > $TMP
        cp $TMP //$OTHER/stargate/$(date +'%a')/
        rm -f $TMP
      else
        [ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
      fi
    }
    

    如果您有任何方式可以看到进一步优化这一点,我想知道 .

  • 0

    怎么样

    for line in `cat`; do
        something($line);
    done
    

相关问题