go version)?$ go version go version go1.12.5 linux/amd64
yes
go env)?go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/wanrui/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/wanrui/workspace"
GOPROXY="https://goproxy.cn"
GORACE=""
GOROOT="/home/wanrui/go"
GOTMPDIR=""
GOTOOLDIR="/home/wanrui/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build173649310=/tmp/go-build -gno-record-gcc-switches"
there is testfile asynccmd .go
package main
import (
"bufio"
"context"
"flag"
"fmt"
_ "net/http/pprof"
"os/exec"
"os/user"
_ "runtime/pprof"
"strconv"
"syscall"
"time"
)
const omg = `ls -al /tmp/ && nohup sleep 1000 &`
func main() {
timeout := flag.Int("timeout", 120, "")
command := flag.String("cmd", omg, "")
runUser := flag.String("user", "www", "")
pgid := flag.Bool("pgid", true, "Setpgid 开关。")
flag.Parse()
ctx, cancle := context.WithTimeout(context.Background(), time.Second*time.Duration(*timeout))
defer cancle()
cmd := exec.CommandContext(ctx, "/bin/bash", "-c", *command)
user, _ := user.Lookup(*runUser)
uid, _ := strconv.Atoi(user.Uid)
gid, _ := strconv.Atoi(user.Gid)
sysProcAttr := &syscall.SysProcAttr{
Setpgid: *pgid,
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
}
cmd.SysProcAttr = sysProcAttr
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
if err := cmd.Start(); err != nil {
fmt.Println("cmd.start occur error", err)
return
}
go func() {
b := bufio.NewReader(stdout)
for {
buf, err := b.ReadBytes('\n')
if err != nil {
fmt.Println("read stdout err:", string(buf), err)
return
}
fmt.Println("read stdout buff:", string(buf))
}
}()
go func() {
b := bufio.NewReader(stderr)
for {
buf, err := b.ReadBytes('\n')
if err != nil {
fmt.Println("read stderr err:", string(buf), err)
return
}
fmt.Println("read stderr buff:", string(buf))
}
}()
if err := cmd.Wait(); err != nil {
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
fmt.Println("cmd.wait occur error ", err)
}
}
there is file : /home/www/.bash_profile
# .bash_profile
# Get the aliases and functions
echo $HOME
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
export PATH
in /root dir run follow command:
./asynccmd -user="www" -cmd=" source /home/www/.bash_profile"
read stdout buff: /home/www
read stdout buff:
read stdout err: java: read |0: file already closed
read stderr err: read |0: file already closed
out is:
read stdout buff: /root
read stdout buff:
read stdout err: java: read |0: file already closed
read stderr err: read |0: file already closed
some properties doesn't set?
Per http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 (emphasis mine):
The system shall initialize this variable at the time of login to be a pathname of the user's home directory.
os/exec is not a login operation, so it doesn't update $HOME for you. I'm not sure whether it updates USER, either.
You can set both explicitly for the command using something like:
cmd.Env = append(os.Environ(), "USER="+user.Username, "HOME="+user.HomeDir)
We probably need clearer documentation about it either way.
CC @bradfitz @kevinburke @ianlancetaylor; see previously #26463.
I don't think os/exec should be in the business of adjusting the environment too much. We do a bit on Windows to make sure there's a minimally functional environment for loading DLLs, but I don't think we should do much more than that.
we hope it can simulate working of shell, as we specified the user. like subprocess in python, it works well.
when Current User is root run shell as another User like flow
sudo -u www sh -c "source /home/www/.bash_profile whereis java"
output like
/home/www
as @bcmills says we can code
like follow
fmt.Println("current:cmd.env", cmd.Env)
fmt.Println("current:os.Environ", os.Environ())
cmd.Env = append(os.Environ(), "USER="+user.Username, "HOME="+user.HomeDir)
sysProcAttr := &syscall.SysProcAttr{
Setpgid: *pgid,
Credential: &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
},
}
os.Environ() is current User root 's ENV , May be cmd.Env should set like follow:
cmd.Env = append(cmd.Env, "USER="+user.Username, "HOME="+user.HomeDir)
I think the most we'll do here is add a small bit of documentation to SysProcAttr or syscall.Credential to say that advanced users are on their own in this regard and we don't do this automatically. Even setting USER and HOME may not be sufficient for all cases. It might be both too much and not enough magic. Better to stay out of it and let advanced users choose exactly what happens without surprises.
Most helpful comment
I don't think os/exec should be in the business of adjusting the environment too much. We do a bit on Windows to make sure there's a minimally functional environment for loading DLLs, but I don't think we should do much more than that.