Linux 如何使脚本显示并将其自身的输出保存为唯一的文件名?

Linux 如何使脚本显示并将其自身的输出保存为唯一的文件名?,linux,bash,Linux,Bash,我有一个基本的I/O脚本,我将调用test.sh,其中包含一系列基于用户输入的各种输出。基本示例如下: echo "Enter username" read USERNAME grep $USERNAME /etc/passwd grep $USERNAME /etc/group echo "Done" exit 0 我需要这个脚本来显示和保存运行时的所有输出。我知道如何使用tee来通过管道将单个命令显示并保存到文件中,例如sh test.sh | tee new.txt,但我不知道如何让脚本

我有一个基本的I/O脚本,我将调用
test.sh
,其中包含一系列基于用户输入的各种输出。基本示例如下:

echo "Enter username"
read USERNAME
grep $USERNAME /etc/passwd
grep $USERNAME /etc/group
echo "Done"
exit 0
我需要这个脚本来显示和保存运行时的所有输出。我知道如何使用tee来通过管道将单个命令显示并保存到文件中,例如
sh test.sh | tee new.txt
,但我不知道如何让脚本本身在运行时显示其整个输出,同时保存其整个输出

我还想让脚本在每次运行时都保存为唯一的文件名,文件名为
USERNAME-DATETIME.rpt
。我试着四处研究,但我能找到的只是如何运行带有重定向和管道的脚本,而不是如何在运行时保存脚本本身


如何显示脚本输出并将其保存到唯一的文件名?

您可以使用以下命令重定向脚本的所有后续输出(到stdout):

(在获得
用户名后插入它)

如果希望重定向stderr和stdout,请使用
快捷方式(仅限bash):

编辑:请注意,
exec 1>文件
将把所有输出重定向到一个文件。若要同时在终端上显示它(复制它),您可以将
exec
重定向与进程替换相结合:

exec &> >(tee "/path/to/$USERNAME-$(date -Is).rpt")
你应该考虑命令:

名字

   script - make typescript of terminal session
概要

   script [options] [file]
描述

   script makes a typescript of everything displayed on your terminal.
   It is useful for students who need a hardcopy record of an
   interactive session as proof of an assignment, as the typescript file
   can be printed out later with lpr(1).

   If the argument file is given, script saves the dialogue in this
   file.  If no filename is given, the dialogue is saved in the file
   typescript.
  • 只需键入
    脚本myfile.txt

  • 执行您想要的任何shell命令

  • 完成后输入

  • 瞧!您的会话将被写入“myfile.txt”。

    作为在所有兼容POSIX的shell中工作的替代方案:

    # note that using "fifo.$$" is vulnerable to symlink attacks; execute only in a directory
    # where you trust no malicious users have write, or use non-POSIX "mktemp -d" to create a
    # directory to put the FIFO in.
    fifo_name="fifo.$$"
    
    mkfifo "fifo.$$"
    tee "/path/to/$USERNAME-$(date "+%Y-%m-%dT%H:%M:%S").rpt" >"fifo.$$" &
    exec >"fifo.$$" 2>&1 # redirects both stdout and stderr; remove the "2>&1" for only stdout
    
    脚本执行完毕后,请确保
    rm-f“fifo.$$”


    在整个脚本的上下文中,加上其他一些最佳实践修复,这可能看起来像:

    #!/bin/sh
    echo "Enter username: " >&2
    read -r username
    
    fifo_name="fifo.$$"
    mkfifo "fifo.$$"
    tee "/path/to/$username-$(date "+%Y-%m-%dT%H:%M:%S").rpt" >"fifo.$$" &
    exec >"fifo.$$" # 2>&1 # uncomment the 2>&1 to also redirect stderr
    
    # look only for exact matches in the correct field, not regexes or substrings
    awk -F: -v username="$username" '$1 == username { print $0 }' /etc/passwd
    awk -F: -v username="$username" '$1 == username { print $0 }' /etc/group
    
    echo "Done" >&2 # human-targeted status messages belong on stderr, not stdout
    
    rm -f "$fifo_name"
    
    注:

    • date-Is
      是一个GNUism,在BSD date中不可用,当然也不保证跨POSIX平台。因此,如果您不知道shell将在哪个平台上运行,那么传递显式格式字符串是安全的
    • grep
      将正则表达式作为子字符串查找——当它被告知查找
      jdoug
      时,它可以找到用户
      jdouglas
      ,或者当它查找
      foo.bar
      时,它可以找到用户
      foo1bar
      (因为点是正则表达式中的通配符)。它还可以与不同字段(带有人名的GECOS字段、主目录等)中的信息进行匹配。告诉
      awk
      在您可以找到的确切字段中查找您的确切字符串要安全得多

    只需将代码用括号括起来,并将其重定向到生成的唯一文件名中即可。(这在所有实际用途中都是独一无二的!)

    #/bin/bash
    
    myuniquefilename=“$(cut-c1-8我相信OP希望内容同时进入文件和磁盘。也许
    exec>>(tee…
    可能合适?也就是说,这肯定是正确的路径。顺便说一句,在“获得用户名后插入它”方面“建议——我实际上会提示stderr,并将
    exec
    放在最开始的位置(好的,就在shebang之后)。提示毕竟不是输出,而是一个状态信息,供人使用;stderr是完全合适的。是的,我需要它在终端上显示并写入文件,但不幸的是,我得到了这个错误:tps-report-gen.sh:第5行:意外标记附近的语法错误
    'tps-report-gen.sh:第5行:
    exec&>>(tee)“$USERNAME.rpt”)'exec 1>工作正常,但出于某种原因,exec&>>(tee…)没有。@CharlesDuffy,但是在文件的最开始,
    USERNAME
    var是未定义的;这就是为什么我说只有在我们知道输出位置后才重定向输出。@Eric,你在使用bash吗?如果是,哪个版本?OP明确要求脚本本身保存日志,而不需要手动干预。调用
    script
    当然是手动干预。仅供参考——所有caps名称都由对shell或操作系统有意义的变量使用,而相关的POSIX规范保证应用程序可以设置任意小写名称,而不会干扰shell本身或POSIX指定实用程序的行为。请参阅,第四段,ke请记住,设置shell变量将覆盖任何类似命名的环境变量。。(顺便说一句,
    grep$USERNAME
    意味着您不仅可以找到
    jdoug
    ,还可以找到
    jdouglas
    ——您确定这就是您想要的吗?此外,由于缺少引号,带空格的用户名可以拆分为多个参数:您需要将其设置为
    grep”$USERNAME“< /代码>以确保名称保持在一起,而不是字符串拆分或全局化;考虑养成运行代码以捕获问题的味道)。这将不起作用。<代码> TE/<代码>挂起试图写入FIFO。您希望“代码> TEE <代码>从FIFO读取(然后复制到文件和STDUT)。:
    tee filename顺便说一句,在您的扩展示例中,我更喜欢
    grep-Fw“$username”/etc/{passwd,group}
    而不是
    awk
    。这更简单,可能是OP想要的,特别是对于组文件(查找所有用户组,而不是用户组)@randomir,我不认为simpler是正确的,如果它失去了将查询映射到实际用户意图的语义。如果有一个名为
    home
    的用户,搜索不应该找到其主目录位于
    /home
    下的每个人。你想匹配成员身份的想法很可能是正确的,但我仍然想做得更好-粒度。我同意你的观点,简单(r)不能代替正确。但这里我们只是猜测OP的意图是什么。:)如果意图是检查
    $username
    的存在,那么我们最好使用
    id-u“$username”
    (符合POSIX),
    # note that using "fifo.$$" is vulnerable to symlink attacks; execute only in a directory
    # where you trust no malicious users have write, or use non-POSIX "mktemp -d" to create a
    # directory to put the FIFO in.
    fifo_name="fifo.$$"
    
    mkfifo "fifo.$$"
    tee "/path/to/$USERNAME-$(date "+%Y-%m-%dT%H:%M:%S").rpt" >"fifo.$$" &
    exec >"fifo.$$" 2>&1 # redirects both stdout and stderr; remove the "2>&1" for only stdout
    
    #!/bin/sh
    echo "Enter username: " >&2
    read -r username
    
    fifo_name="fifo.$$"
    mkfifo "fifo.$$"
    tee "/path/to/$username-$(date "+%Y-%m-%dT%H:%M:%S").rpt" >"fifo.$$" &
    exec >"fifo.$$" # 2>&1 # uncomment the 2>&1 to also redirect stderr
    
    # look only for exact matches in the correct field, not regexes or substrings
    awk -F: -v username="$username" '$1 == username { print $0 }' /etc/passwd
    awk -F: -v username="$username" '$1 == username { print $0 }' /etc/group
    
    echo "Done" >&2 # human-targeted status messages belong on stderr, not stdout
    
    rm -f "$fifo_name"
    
    #!/bin/bash
    
    myuniquefilename="$( cut -c1-8 <( date "+%S%N" | md5sum ))"
    
    echo "Enter username"
    read USERNAME
    {
        echo "Executing. ..."
        date 
        grep $USERNAME ./etc/passwd
        grep $USERNAME ./etc/group
    } > ${myuniquefilename}.out
    
    echo "Done"
    exit 0