如何使用sudo将输出重定向到我没有写入权限的位置?

1113

我在我们开发的RedHat Linux盒子上被授予sudo访问权限,而我似乎经常需要将输出重定向到通常没有写入权限的位置。

麻烦的是,这个人为的例子不起作用:

sudo ls -hal /root/ > /root/test.out

我刚刚收到响应:

-bash: /root/test.out: Permission denied

我该如何让这个工作?


1
使用 chmod u+w filename。 - Szabolcs Dombi
@DombiSzabolcs 你建议我先以sudo身份创建文件,然后再给自己权限?好主意。 - Jonathan
2
在很多情况下,您会因为问了“为什么我会收到权限被拒绝的错误信息?”而来到这里。有时答案是确实需要以“root”身份创建文件(在这种情况下,请继续阅读答案),但很多时候,您只需要在其他位置以自己的身份创建文件即可。 - tripleee
3
在经过一番探索后,我最终选择将结果重定向到一个临时文件,然后使用sudo命令将其移动到目标位置。 - TingQian LI
请参见 https://dev59.com/UXVD5IYBdhLWcg3wHnsX?rq=1 - Nate Eldredge
不!出于安全原因,通常不建议打开需要sudo权限的文件的权限。如果您拥有sudo权限,请使用sudo bash(或您喜欢的shell)在该shell中执行操作。 - Noel Yap
15个回答

1535

你的命令无法正常执行,因为重定向是由你的shell执行的,而该shell没有写入 /root/test.out 的权限。输出的重定向不是通过sudo执行的。

有多种解决方案:

  • 使用-c选项以sudo身份运行shell,并将命令交给它:

    sudo sh -c 'ls -hal /root/ > /root/test.out'
    
  • 创建一个包含你的命令的脚本,并使用sudo运行该脚本:

  • #!/bin/sh
    ls -hal /root/ > /root/test.out
    

    运行sudo ls.sh命令。如果不想创建临时文件,请参考Steve Bennett的回答

  • 使用sudo -s启动shell,然后运行您的命令:

  • [nobody@so]$ sudo -s
    [root@so]# ls -hal /root/ > /root/test.out
    [root@so]# ^D
    [nobody@so]$
    
    使用 sudo tee (如果在使用 -c 选项时需要进行大量转义):
    sudo ls -hal /root/ | sudo tee /root/test.out > /dev/null
    
    为了防止tee在屏幕上输出,必须重定向到/dev/null。如果要进行追加输出而不是覆盖原有内容,则使用tee -atee --append(最后一个选项仅适用于GNU coreutils)。感谢JdAdam J. ForsterJohnathan提供第二、三和四种解决方案。

3
这里有一个很好的答案告诉你如何分别重定向标准错误输出和标准输出:https://dev59.com/vHRB5IYBdhLWcg3wLk1M ... 基本上是 perl -e 'print "STDIN\n"; print STDERR "STDERR\n"; ' > >( tee stdout.log ) 2> >( tee stderr.log >&2 ) - curious_prism
2
在使用外壳命令时(例如在脚本中使用),您需要执行“sudo -E ...”以使用变量。 - Urhixidur
3
将tee的输出重定向到/dev/null在许多情况下可能是不必要的,因为将其回显到屏幕上并不会对内容造成影响。例如,当仅需要处理常规命令的输出或小文本文件的内容时。 - thomasrutter
tee 以外,我发现另一个好的解决方案是在 heredoc 语句中使用 sudo bash <<EOF echo -e "$(whoami)\n$(ls -l)\n$(pstree -sp $$)" > a whoami pstree -sp $$ EOF,此时 shell 扩展可以正常工作,并允许在非 root 环境下执行命令,最终将所有结果重定向为 sudo 到特权文件,这里文件 a 的所有者是 root。 - dinesh saini

140

这里有人建议使用sudo tee:

sudo ls -hal /root/ | sudo tee /root/test.out > /dev/null

这也可以用于重定向任何命令到一个你没有访问权限的目录。它起作用是因为 tee 程序实际上是一个“输出到文件”的程序,而对 /dev/null 的重定向是为了防止它也输出到屏幕上,以使它与上面的原始例子保持相同。


13
在许多情况下,即使普通用户有执行命令的权限,但是“只有”不能写入所需的输出文件,则第一个sudo(即用于命令本身)可能会被省略。 - Hagen von Eitzen
还有moreutils包中的sponge工具。它的功能与tee完全相同,唯一的区别是它不会输出到stdout,因此您可以不必将输出重定向到/dev/null。所以您只需输入ls -hal /root/ | sudo sponge /root/test.out即可。 - cyqsimon

100

我自己想出来的一个技巧是

sudo ls -hal /root/ | sudo dd of=/root/test.out

21
上面的 sudo tee /root/file > /dev/null 的例子不如使用 sudo dd 更好! - kristianlm
17
dd 不是一个奇怪和晦涩的命令。当你需要在两个块设备之间进行带缓冲的大量数据拷贝时,就会用到它。其语法相当简单:dd 是命令名称,of=/root/test.out 是参数,告诉 dd 输出文件的位置。 - rhlee
15
@steve 直到你了解它是什么,所有的东西都是“模糊的”。 of 意味着输出文件,而 dd 是在Linux和OSX中广泛使用的工具(主要用于将映像写入磁盘)。这确实是一个不错的技巧。 - blockloop
14
在这种情况下,“dd”可能和“tee”一样有用。在两种情况下,您都使用通用且众所周知的命令来达到一个略微不同于其原始目的但仍为人所知和记录的目的。虽然“dd”擅长复制大量数据,但它也适用于少量数据。它在此处的好处是不会将输出回显到标准输出。 - thomasrutter
38
当您在输入 sudo dd 这些单词时,一定要非常、非常确保其后面的参数是正确的(特别是考虑到其非标准语法)。毫不夸张地说,它的名字叫做“磁盘毁灭者”... - ali_m
显示剩余6条评论

65

问题在于命令是在sudo下运行的,但重定向是在您的用户下运行的。这是由shell完成的,您几乎无法对其进行任何更改。

sudo command > /some/file.log
`-----v-----'`-------v-------'
   command       redirection

绕过此限制的通常方法有:

  • 将命令包装在一个脚本中,然后在sudo下调用该脚本。

    如果命令和/或日志文件发生更改,您可以使脚本接受这些变量作为参数。例如:

sudo log_script command /log/file.txt
呼叫一个Shell并通过-c参数将命令行作为参数传递。 这对于一次性的复合命令特别有用。例如:
sudo bash -c "{ command1 arg; command2 arg; } > /log/file.txt"
  • 安排一个具备所需权限(例如sudo)的管道/子Shell

  • # Read and append to a file
    cat ./'file1.txt' | sudo tee -a '/log/file.txt' > '/dev/null';
    
    # Store both stdout and stderr streams in a file
    { command1 arg; command2 arg; } |& sudo tee -a '/log/file.txt' > '/dev/null';
    

    31

    又是主题的另一种变化:

    sudo bash <<EOF
    ls -hal /root/ > /root/test.out
    EOF
    

    或者当然:

    echo 'ls -hal /root/ > /root/test.out' | sudo bash
    

    这样做的一个(微小的)优点是您不需要记忆任何 sudosh/bash 的参数。


    27

    稍微解释一下为什么使用tee选项更好

    假设您具有执行创建输出命令的适当权限,如果将命令输出导向到tee,则只需要使用sudo提升tee的特权,并指示tee将内容写入(或追加)到相应的文件中。

    在问题中给出的示例中,这意味着:

    ls -hal /root/ | sudo tee /root/test.out
    

    再举几个实际例子:

    # kill off one source of annoying advertisements
    echo 127.0.0.1 ad.doubleclick.net | sudo tee -a /etc/hosts
    
    # configure eth4 to come up on boot, set IP and netmask (centos 6.4)
    echo -e "ONBOOT=\"YES\"\nIPADDR=10.42.84.168\nPREFIX=24" | sudo tee -a /etc/sysconfig/network-scripts/ifcfg-eth4
    

    在这些例子中,您正在获取非特权命令的输出并将其写入通常只能由root写入的文件中,这是您提出问题的原因。

    以这种方式执行是一个好主意,因为生成该输出的命令未使用提升的权限来执行。对于使用您不完全信任的脚本作为源命令时,这似乎并不重要,但这是至关重要的。

    请注意,您可以使用 -a 选项将 tee 用于追加(类似于 >>)到目标文件,而不是覆盖它(类似于 >)。


    抱歉js3,但这个建议已经在2008年被提出,并且位列第二高的答案:https://dev59.com/eXVD5IYBdhLWcg3wHnwX#82553。 - Jonathan
    3
    没错,Jonathan,我会更新我的回答并详细阐述为什么这是更好的选择。感谢你的有益反馈。 - jg3

    26
    让sudo运行一个shell,像这样:

    请执行以下命令:

    sudo sh -c "echo foo > ~root/out"
    

    15

    我处理这个问题的方式是:

    如果您需要编写/替换文件:

    echo "some text" | sudo tee /path/to/file
    
    如果您需要向文件追加内容:
    echo "some text" | sudo tee -a /path/to/file
    

    2
    这与上面的答案有何实质性不同? - Jonathan
    2
    它并不是“实质上不同”,但它澄清了替换和追加文件之间的不同用法。 - jamadagni
    2
    这是我复制到我的作弊纸上的答案。 - MortimerCat

    14

    不是想要重复说一遍,但这里有太多使用tee的答案,这意味着你必须将stdout重定向到/dev/null,除非你想在屏幕上看到一份副本。

    一个更简单的解决方案是只使用cat

    sudo ls -hal /root/ | sudo bash -c "cat > /root/test.out"
    

    注意重定向符号放在引号中,这样它将被由sudo启动的一个shell解释而非当前shell解释。


    7
    我认为它没有得到太多的关注,因为它并不比sudo bash -c "ls -hal /root > /root/test.out"好多少。使用tee命令可以避免需要shell,而cat却做不到。 - Nick Russo

    6

    写一个脚本怎么样?

    文件名:myscript

    #!/bin/sh
    
    /bin/ls -lah /root > /root/test.out
    
    # end script
    

    然后使用sudo运行脚本:
    sudo ./myscript
    

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