kurumi-bioの雑記帳

プログラミング、パソコン、ペット、 犬、お出かけ

初心者のGo言語 -33- <time.Sleep,Run>

こんにちは、kurumi-bioです。
第4回目のexecパッケージ(標準ライブラリー)の学習です。

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20.3 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.1 linux/amd64

[time package] Sleep関数

func Sleep(d Duration)

関数の説明スリープは、現在のゴルーチンを少なくともd期間一時停止します。負またはゼロの期間は、Sleep をすぐに復帰させます。

◆テストコード

package main

import (
    "fmt"
    "os"
    "strconv"
    "time"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "usage: sleep ms")
        os.Exit(1)
    }
    i, _ := strconv.Atoi(os.Args[1])
    t := time.Duration(i)
    time.Sleep(t * time.Second)
    fmt.Fprintf(os.Stderr, "Success")
}

コードの説明 Cmd.Run()関数を試すためのテストコードです。
timeパッケージのSleep関数を使って、起動時の引数で指定された期間(秒)一時停止します。
正常に完了した場合、一時停止後に標準エラー出力に"Success"と出力します。 起動時の引数が足りない場合は、標準エラー出力にusageを出力します。

◆実行結果(Windows)

実行結果の説明 実行時の引数を指定した場合は、指定した期間経過後に標準エラー出力Successと出力されます。
実行時の引数を指定しなかった場合は、標準エラー出力usage: Sleep 秒と出力されます。

Run関数

func (c *Cmd) Run() error

関数の説明Run は、指定されたコマンドを開始し、完了するまで待機します。
コマンドが実行され、stdin、stdout、および stderr のコピーに問題がなく、ゼロの終了ステータスで終了する場合、返されるエラーは nil です。
コマンドが開始しても正常に完了しない場合、エラーのタイプは *ExitError です。他の状況では、他のエラー タイプが返される場合があります。
呼び出し元のゴルーチンが runtime.LockOSThread でオペレーティング システム スレッドをロックし、継承可能な OS レベルのスレッド状態 (Linux または Plan 9名前空間など) を変更した場合、新しいプロセスは呼び出し元のスレッド状態を継承します。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
    "time"
)

func main() {
    c := exec.Command("./Sleep.exe", "3")
    c.Stderr = os.Stderr
    n := time.Now()
    fmt.Println("Start")
    e := c.Run()
    fmt.Printf("End:[%1.1f 秒経過]\n", time.Since(n).Seconds())
    if e != nil {
        fmt.Fprint(os.Stderr, "error=[%v]\n", e)
        fmt.Println(e)
    }
}

コードの説明 Sleep.exeコマンドを実行して、Cmd.Run()関数の実行時間(秒)を出力するプログラムです。

◆実行結果(Windows)

実行結果の説明 経過秒数がSleep.exeコマンドの起動引数と同じになっていることから、 Cmd.Run()関数は、コマンドが完了するまで待機していることがわかります。
また、標準エラー出力への出力のあるなしは関係なく、Sleep.exeコマンドの終了ステータスが0なので、戻り値のエラーはnilになっています。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    c := exec.Command("./sleep.exe")
    c.Stderr = os.Stderr
    e := c.Run()

    if e != nil {
        var ee *exec.ExitError
        var flag bool
        ee, flag = e.(*exec.ExitError)
        if flag {
            fmt.Println("")
            fmt.Printf("ExitCode=[%d]\n", ee.ProcessState.ExitCode())
            fmt.Printf("Pid=[%d]\n", ee.ProcessState.Pid())
            fmt.Printf("Stderr=[%s]\n", string(ee.Stderr))
        }
        fmt.Fprintf(os.Stderr, "Error=[%v]\n", e)
    }
}

コードの説明 Sleep.exeコマンドを引数なしで実行します。
戻り値のエラーに値がある場合は、エラーが*exec.ExitError型の場合はExitCodeなどを出力します。

◆実行結果(Windows)

実行結果の説明 Sleep.exeコマンドを引数なしで実行したため、標準エラー出力にusageが出力されました。
さらに終了ステータスが1なので、戻り値のエラーに*ExitError型の値が入りました。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    c := exec.Command("./sleep2.exe")
    c.Stderr = os.Stderr
    e := c.Run()

    if e != nil {
        var ee *exec.ExitError
        var flag bool
        ee, flag = e.(*exec.ExitError)
        if flag {
            fmt.Println("")
            fmt.Printf("ExitCode=[%d]\n", ee.ProcessState.ExitCode())
            fmt.Printf("Pid=[%d]\n", ee.ProcessState.Pid())
            fmt.Printf("Stderr=[%s]\n", string(ee.Stderr))
        }
        fmt.Fprintf(os.Stderr, "Error=[%v]\n", e)
    }
}

コードの説明 存在しないコマンド(Sleep2.exe)コマンドを実行します。

◆実行結果(Windows)

実行結果の説明 file does not existが戻り値のエラーに返されました。 コマンドが実行されなかったため終了ステータスが取得できず*ExitError以外の型になりました。

最後までご覧いただきありがとうございます

初心者のGo言語 -32- <CombinedOutput,Environ,Output>

こんにちは、kurumi-bioです。
第3回目のexecパッケージ(標準ライブラリー)の学習です

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.1 linux/amd64

CombinedOutput関数

func (c *Cmd) CombinedOutput() ([]byte, error)

関数の説明CombinedOutput はコマンドを実行し、その結合された標準出力と標準エラーを返します。

◆テストコード(printOut.go)

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Fprint(os.Stdout, "stdout!")
    fmt.Fprint(os.Stderr, "stderr!")
}

コードの説明 標準出力に"stdout!"と出力し、標準エラー出力に"stderr!"と出力するプログラムです。
go build printOut.goでビルドして実行形式にしておきます。

◆テストコード(CombinedOutputTest.go)

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    cmd := exec.Command("./printOut.exe")
    b, e := cmd.CombinedOutput()
    if e != nil {
        fmt.Printf("err=%v\n", e)
    }
    fmt.Printf("[%s]\n", b)
}

コードの説明 先ほどのビルドで生成したprintOut.exeコマンドを実行し、結合された標準出力と標準エラーを出力するプログラムです。

◆実行結果(Windows)

実行結果の説明 標準出力と標準エラーが結合して出力されました。

Environ関数

func (c *Cmd) Environ() []string

関数の説明Environ は、現在構成されているコマンドが実行される環境のコピーを返します。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    if len(os.Args) != 2 {
        os.Exit(1)
    }
    cmd := exec.Command(os.Args[1])
    s := cmd.Environ()
    for i, v := range s {
        fmt.Printf("Environ[%d]=[%s]\n", i, v)
    }
}

コードの説明 実行時のコマンド引数で指定したコマンドを実行し、環境を出力します。

◆実行結果(Windows)

実行結果の説明 存在しないコマンドを指定しましたが、環境が出力されました。

Output関数

func (c *Cmd) Output() ([]byte, error)

関数の説明出力はコマンドを実行し、その標準出力を返します。返されるエラーは通常、*ExitError 型です。 c.Stderr が nil の場合、Output は ExitError.Stderr を設定します。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
    "strings"
)

func main() {
    if len(os.Args) != 3 {
        os.Exit(1)
    }

    var serr strings.Builder

    cmd := exec.Command(os.Args[1], os.Args[2])
    cmd.Stderr = &serr

    o, e := cmd.Output()
    if e != nil {
        fmt.Fprintf(os.Stderr, "ERROR=[%v]\n", e)
        fmt.Printf("stderr=[%s]\n", serr.String())
    }
    fmt.Printf("o=[%s]\n", o)
}

コードの説明 実行時のコマンド引数で指定したコマンドを実行し、戻り値の標準出力の内容を出力します。
エラーが発生した場合は、エラー内容とcmd.stderrの内容を出力します。

◆実行結果(Windows)

実行結果の説明 goコマンドにコマンドライン引数versionを渡して実行しました。
標準出力に渡されたgoコマンドの実行結果のバージョン番号が出力されました。

◆実行結果(Windows)

実行結果の説明 存在しないコマンドgo1コマンドライン引数versionを渡して実行しました。
cmd.Output関数の戻り値errの値がexecutable file not found in %PATH%(%PATH%に実行可能ファイルが見つかりません)になりました。

◆実行結果(Windows)

実行結果の説明 goコマンドに存在しないコマンドライン引数verを渡して実行しました。
cmd.Output関数の戻り値errの値がexit status 2になり、
cmd.Stderrgoコマンドのエラーgo ver: unknown commandが出力されました。

最後までご覧いただきありがとうございます

初心者のGo言語 -31- <Command>

こんにちは、kurumi-bioです。
第2回目のexecパッケージ(標準ライブラリー)の学習です

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.1 linux/amd64

Cmd型

type Cmd struct {
    // Path は、実行するコマンドのパスです。
    Path string
    // Args は、コマンドを Args[0] として含むコマンド ライン引数を保持します。
    Args []string
    // Env は、プロセスの環境を指定します。
    Env []string
    // Dir は、コマンドの作業ディレクトリを指定します。
    Dir string
    // Stdin は、プロセスの標準入力を指定します。
    Stdin io.Reader
    // Stdout と Stderr は、プロセスの標準出力とエラーを指定します。
    Stdout io.Writer
    Stderr io.Writer
    // ExtraFiles は、新しいプロセスによって継承される追加のオープン ファイルを指定します。
    ExtraFiles []*os.File
    // SysProcAttr は、オプションのオペレーティング システム固有の属性を保持します。
    SysProcAttr *syscall.SysProcAttr
    // Process は、いったん開始されると、基礎となるプロセスです。
    Process *os.Process
    // ProcessState には、終了したプロセスに関する情報が含まれています
    ProcessState *os.ProcessState
    // LookPath エラー (存在する場合)。
    Err error
    Cancel func() error
    WaitDelay time.Duration
}

type Cmdの説明 Cmd は、準備中または実行中の外部コマンドを表します。 Run、Output、または CombinedOutput メソッドを呼び出した後、Cmd を再利用することはできません。

Command関数

func Command(name string, arg ...string) *Cmd

変数の説明Command は、指定された引数で指定されたプログラムを実行する Cmd 構造体を返します。返された構造体に Path と Args のみを設定します。 name にパス セパレータが含まれていない場合、Command は LookPath を使用して、可能であれば name を完全なパスに解決します。それ以外の場合は、名前をパスとして直接使用します。 返された Cmd の Args フィールドは、コマンド名とそれに続く arg の要素から構成されるため、arg にコマンド名自体を含めることはできません。たとえば、Command("echo", "hello") です。 Args[0] は常に名前であり、解決される可能性のあるパスではありません。 Windows では、プロセスはコマンド ライン全体を 1 つの文字列として受け取り、独自の解析を行います。 Command は、CommandLineToArgvW (最も一般的な方法) を使用するアプリケーションと互換性のあるアルゴリズムを使用して、Args を組み合わせてコマンド ライン文字列に引用します。注目すべき例外は msiexec.exe と cmd.exe (したがって、すべてのバッチ ファイル) であり、引用符を外すアルゴリズムが異なります。これらのケースまたは他の同様のケースでは、自分で引用を行い、SysProcAttr.CmdLine で完全なコマンド ラインを指定して、Args を空のままにすることができます。

◆テストコード

package main

import (
    "errors"
    "fmt"
    "os"
    "os/exec"
    "strings"
)

func main() {
    if len(os.Args) != 3 {
        os.Exit(1)
    }
    var sout, serr strings.Builder
    c := exec.Command(os.Args[1], os.Args[2])
    c.Stdout = &sout
    c.Stderr = &serr

    e := c.Run()

    if e != nil {
        if errors.Is(e, exec.ErrDot) {
            fmt.Fprintln(os.Stderr, "ErrorDot")
        } else if errors.Is(e, exec.ErrNotFound) {
            fmt.Fprintln(os.Stderr, "ErrNotFound")
        } else if errors.Is(e, exec.ErrWaitDelay) {
            fmt.Fprintln(os.Stderr, "ErrWaitDelay")
        }
        fmt.Fprintf(os.Stderr, "ERROR=[%v]\n", e)
    }
    fmt.Printf("stdout=[%s]\n", sout.String())
    fmt.Printf("stderr=[%s]\n", serr.String())
}

コードの説明 指定された実行ファイルを指定した引数で実行し、stdoutstderrの内容を出力します。 Cmd.Run()関数の実行時にエラーが発生した場合は、エラー内容と該当するerrors.Isの値を表示します。

◆実行結果(Windows)

実行結果の説明 goコマンドをversion引数で実行しました。環境変数PATHが通っているのでgoコマンドが実行され標準出力(stdout)にバージョンが出力されました。

実行結果の説明 goコマンドに存在しない引数を指定して実行しました。goコマンドのエラーメッセージが標準エラー出力(stderr)に出力されました。

実行結果の説明 存在しないファイルを指定すると、executable file not found in %PATH%(%PATH%に実行可能ファイルが見つからない)というエラーが発生しました。

次に少し手を加えてexec.Command関数の戻り値Cmd構造体の内容を表示するプログラムを作成します。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
    "strings"
)

func writeCmd(c exec.Cmd) {
    fmt.Printf("Path=[%s]\n", c.Path)
    for i, v := range c.Args {
        fmt.Printf("Args[%d]=[%s]\n", i, v)
    }
    p := c.Process
    if p != nil {
        if p != nil {
            fmt.Printf("pid=[%d]\n", c.Process.Pid)
        }
    }
}

func main() {
    if len(os.Args) != 3 {
        os.Exit(1)
    }
    var sout, serr strings.Builder

    c := exec.Command(os.Args[1], os.Args[2])
    c.Stdout = &sout
    c.Stderr = &serr

    e := c.Run()
    writeCmd(*c)

    if e != nil {
        fmt.Fprintf(os.Stderr, "ERROR=[%v]\n", e)
    }
    fmt.Printf("stdout=[%s]\n", sout.String())
    fmt.Printf("stderr=[%s]\n", serr.String())
}

コードの説明 exec.Cmd構造体の実行パス、実行時の引数、プロセスIDを出力します。 プロセスIDを出力するのでCmd.Run()関数でモジュール実行後に構造体の内容を出力しています。

◆実行結果(Windows)

実行結果の説明 goコマンドのパス、実行時の引数、プロセスIDが出力されました。

最後までご覧いただきありがとうございます

初心者のGo言語 -30- <LookPath>

こんにちは、kurumi-bioです。
第1回目のexecパッケージ(標準ライブラリー)の学習です。

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.1 linux/amd64

Variables(変数)

var ErrDot = errors.New("cannot run executable found relative to current directory")

変数の説明ErrDot は、暗黙的または明示的に「.」がパスに含まれているため、パス ルックアップが現在のディレクトリの実行可能ファイルに解決されたことを示します。詳細については、パッケージのドキュメントを参照してください。このパッケージの関数は ErrDot を直接返さないことに注意してください。コードでは、err == ErrDot ではなく、errors.Is(err, ErrDot) を使用して、返されたエラー err がこの状態によるものかどうかをテストする必要があります。

var ErrNotFound = errors.New("executable file not found in $PATH")

変数の説明ErrNotFound は、パス検索で実行可能ファイルが見つからなかった場合に発生するエラーです。

var ErrWaitDelay = errors.New("exec: WaitDelay expired before I/O complete")

変数の説明ErrWaitDelay は、(*Cmd).Wait によって返されます (プロセスが正常なステータス コードで終了したが、コマンドの WaitDelay が期限切れになる前にその出力パイプが閉じられていない場合)。

LookPath関数

func LookPath(file string) (string, error)

変数の説明LookPath は、PATH 環境変数で指定されたディレクトリで file という名前の実行可能ファイルを検索します。ファイルにスラッシュが含まれている場合は、直接試行され、PATH は参照されません。それ以外の場合、成功すると、結果は絶対パスになります。

古いバージョンの Go では、LookPath は現在のディレクトリからの相対パスを返すことができました。 Go 1.19 以降、LookPath は代わりに、errors.Is(err, ErrDot) を満たすエラーと共にそのパスを返します。詳細については、パッケージのドキュメントを参照してください。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage: LookPathTest.exe fileName")
        os.Exit(1)
    }
    s, e := exec.LookPath(os.Args[1])
    if e != nil {
        fmt.Fprintf(os.Stderr, "ERROR=[%v]\n", os.NewSyscallError("os.LookPath", e))
        os.Exit(1)
    } else {
        fmt.Printf("path=[%s]\n", s)
    }
}

コードの説明 実行時の引数で指定したファイルの検索結果を出力します。

◆実行結果(Windows)

実行結果の説明 存在しないファイルを指定すると、executable file not found in %PATH%(%PATH%に実行可能ファイルが見つからない)というエラーが発生しました。

実行結果の説明 goコマンドのファイルを検索するとパスが戻ってきました。実際に存在するファイル名はgo.exeですが、goでも同じ結果になりました。

実行結果の説明 カレントディレクトリに存在するLookPathTest.exeを指定しましたが、cannot run executable found relative to current directory(現在のディレクトリに関連して見つかった実行可能ファイルを実行できません)のエラーが発生しました。
関数の説明に下記記載がありましたので、errors.Is関数を使って試してみます。

古いバージョンの Go では、LookPath は現在のディレクトリからの相対パスを返すことができました。 Go 1.19 以降、LookPath は代わりに、errors.Is(err, ErrDot) を満たすエラーと共にそのパスを返します。詳細については、パッケージのドキュメントを参照してください。

◆テストコード

package main

import (
    "errors"
    "fmt"
    "os"
    "os/exec"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage: LookPathTest.exe fileName")
        os.Exit(1)
    }
    s, e := exec.LookPath(os.Args[1])
    if s != "" {
        fmt.Printf("path=[%s]\n", s)
    }
    if e != nil {
        if errors.Is(e, exec.ErrDot) {
            fmt.Fprintln(os.Stderr, "ErrorDot")
        } else if errors.Is(e, exec.ErrNotFound) {
            fmt.Fprintln(os.Stderr, "ErrNotFound")
        } else if errors.Is(e, exec.ErrWaitDelay) {
            fmt.Fprintln(os.Stderr, "ErrWaitDelay")
        }
        fmt.Fprintf(os.Stderr, "ERROR=[%v]\n", os.NewSyscallError("os.LookPath", e))
    }
}

コードの説明 実行時の引数で指定したファイルの検索結果を出力します。
戻り値のstringに値が存在する場合は、stringをパスとして出力します。
戻り値のerrorに値が存在する場合は、errors.Is関数のエラー判断結果を出力します。

◆実行結果(Windows)

実行結果の説明カレントディレクトリに存在するLookPathTest.exeを指定した結果、ファイル名が表示されてexec.ErrDotが真になりました。
存在するファイルですが、戻り値のerrに値が入ってしまう点は注意ですね。

実行結果の説明存在しないファイルを指定すると戻り値のstringが空でexec.ErrNotFoundが真になりました。

◆実行結果(Linux)

実行結果の説明Linuxでは、実行権(x)が無いファイルを指定するとpermission deniedが発生しました。

実行結果の説明実行権(x)を付与することでファイルを見つけることができました。

最後までご覧いただきありがとうございます

初心者のGo言語 -29- <Args,Lchown>

こんにちは、kurumi-bioです。
第13回目のosパッケージ(標準ライブラリー)の学習です。

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.1 linux/amd64

Args変数

var Args []string

変数の説明Args は、プログラム名で始まるコマンドライン引数を保持します。

◆テストコード

package main

import (
    "fmt"
    "os"
)

func main() {
    for i, v := range os.Args {
        fmt.Printf("Args[%d]=%s\n", i, v)
    }
}

コードの説明 os.Args変数の値をすべて表示します。

◆実行結果(Windows)

実行結果の説明go buildコマンドでEXEモジュールを作成してから実行しました。
コマンドに引数を3つ渡して実行した結果os.Args変数の値は、 コマンドパス、1つ目の引数、2つ目の引数、3つ目の引数になりました。

Lchown関数

func Lchown(name string, uid, gid int) error

関数の説明 Lchown は、指定されたファイルの数値 uid と gid を変更します。ファイルがシンボリック リンクの場合、リンク自体の uid と gid が変更されます。エラーがある場合、タイプは *PathError になります。

Windows では、*PathError でラップされた syscall.EWINDOWS エラーが常に返されます。

◆テストコード

package main

import (
    "fmt"
    "os"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "ファイルを指定してください")
        os.Exit(1)
    }

    fn := os.Args[1]
    e := os.Lchown(fn, 1001, 1000)
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Lchown", e))
        os.Exit(1)
    } else {
        fmt.Printf("ファイル[%s]のuidとgidを変更しました。\n", fn)
    }
}

コードの説明 os.Lchown()関数を使って、コマンド実行時に指定したファイルのuidを1001、gidを1000に変更します。

◆実行結果(Windows)

実行結果の説明関数の説明通りnot supoprted by windowsのエラーになりました。

◆実行結果(Linux)

普通のファイルとハードリンクとシンボリックリンクの3種類のファイルに対して、uidとgidを変更します。

statコマンドを使って、test-hファイルとhardlinkファイルは同一ファイル(ハードリンク)であることを確認

symboliclinkファイルは、test-sファイルのシンボリックリンクであること。
全てのファイルは、uidがkurumiでgidがuserであること。
を確認。

uid:1001は、goというユーザーであること。
gid:1000は、go-userというグループであること。
を確認

testファイルのuidとgidを変更

hardlinkファイルのuidとgidを変更。リンク先のtest-hファイルもuidとgidが変更されています。

symboliclinkファイルのuidとgidを変更。リンク先のtest-sファイルはuidとgidは変更されませんでした。

実行結果の説明 uidとgidが指定したものに変わっていることが確認できました。 ハードリンクは、リンク先も変わっていることが確認できました。 シンボリックリンクは、リンク先は変化がなく、リンクファイルだけuidとgidが変わることが確認できました。

最後までご覧いただきありがとうございます

初心者のGo言語 -28- フォルダのパーミッション(権限)を調べる

こんにちは、kurumi-bioです。

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20 windows/amd64

  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.2 linux/amd64

はじめに

Linuxパーミッション(権限)は、
r:読み取り可能 / w:書き込み可能 / x:実行可能
が指定可能です。
ファイルのパーミッション(権限)は、操作と対応しているで理解できますが、
フォルダの場合は、いまいちわからないです。
ですので、今まで学習してきたfmtパッケージとosパッケージを使って、 パーミッション(権限)ごとに可能な操作を確認するプログラムを作ってみます。

試すこと

パーミッション(権限)を変化させながら下記の操作の可否を試します。

  • フォルダ直下のリストが取得可能か
  • フォルダ移れるか
  • フォルダ直下のファイルの読み込みが可能か
  • フォルダ直下にファイルの書き込みが可能か
  • フォルダ直下のファイルが削除できるか

結果

ソースコードが長いので先に結果を記載します。

  • r
    • 直下のリスト取得の可否
  • w
    • 単体では意味がない
    • Xと組み合わせてフォルダ内のファイル書き込みの可否
  • x
    • フォルダに移る可否
    • フォルダ内のファイル読み込みの可否

テストコード

今回はソースコードが長いので、2つのファイルで作成することにしました。

◆テストコード -1-

package main

import (
    "fmt"
)

func main() {
    const dirName = "test"
    readTestFilePath := fmt.Sprintf("%s/readTest.txt", dirName)
    writeTestFilePath := fmt.Sprintf("%s/writeTest.txt", dirName)

    fmt.Println("             |ls |cd | W | R | D |")

    for i := 0; i <= 700; i = i + 100 {
        s := fmt.Sprintf("%03d", i)
        //前処理(準備)
        md(dirName)           // テスト用のフォルダを作成
        wft(readTestFilePath) // Readテスト用のファイルを作成
        cm(dirName, s)        // フォルダのパーミッションを変更
        pm(dirName)           // フォルダのパーミッションを出力

        //試すこと
        dd(dirName)            // フォルダ直下のリスト取得の可否を出力
        ch(dirName)            // フォルダ移動の可否を出力
        wfp(writeTestFilePath) // フォルダ直下のファイル書き込みの可否を出力
        rf(readTestFilePath)   // フォルダ直下のファイル読み込みの可否を出力
        rm(readTestFilePath)   // フォルダ直下のファイル削除の可否を出力
        fmt.Println("")

        //後処理(片付け)
        cm(dirName, "0700") // フォルダのパーミッションをフルアクセスに変更
        ra(dirName)         // フォルダの削除

    }
}

コードの説明 forループで、フォルダの所有者のパーミッション(権限)を変化させるため000~700まで100刻みでパーミッション(権限)を変化させます。
forループ内で、動作確認テストの前準備として、パーミッション(権限)を確認するためのテストフォルダを作成と読み込みテスト用のファイルを作成します。
その後、試したかった操作を一通り行います。
最後に、テストフォルダを削除するためパーミッション(権限)をフルアクセス可能にしてからフォルダごと削除しています。

◆広告(中):ディスプレイ広告

広告の下に続きます。

◆テストコード -2-

package main

import (
    "fmt"
    "os"
    "strconv"
)

// 引数で指定したフォルダをサブフォルダを含め削除する
func ra(fileName string) {
    os.RemoveAll(fileName)
}

// 引数で指定したファイルを削除する
func rm(fileName string) {
    e := os.Remove(fileName)
    if e != nil {
        fmt.Print("| X |")
    } else {
        fmt.Print("| O |")
    }
}

func wft(filePath string) {
    wf(filePath, true)
}
func wfp(filePath string) {
    wf(filePath, false)
}
// 引数で指定したパスでファイルを作成する
func wf(filePath string, isTest bool) {
    e := os.WriteFile(filePath, []byte("Write Test"), 0666)
    if !(isTest) {
        if e != nil {
            fmt.Print("| X ")
        } else {
            fmt.Print("| O ")
        }
    }
}

// 引数で指定したファイルを読み込む
func rf(filePath string) {
    _, e := os.ReadFile(filePath)
    if e != nil {
        fmt.Print("| X ")
    } else {
        fmt.Print("| O ")
    }
}

// 引数で指定したフォルダ直下のファイルリストを取得する
func dd(dirPath string) {
    _, e := os.ReadDir(dirPath)
    if e != nil {
        fmt.Print("| X ")
    } else {
        fmt.Print("| O ")
    }
}

// 引数で指定したフォルダに変更する
func ch(dirPath string) {
    e := os.Chdir(dirPath)
    if e != nil {
        fmt.Print("| X ")
    } else {
        fmt.Print("| O ")
        os.Chdir("../")
    }
}

// 引数で指定したフォルダを作成する
func md(dirPath string) {
    e := os.Mkdir(dirPath, 0750)
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Mkdir", e))
    }
}

// 引数で指定したフォルダのモードを引数で指定したモードに変更する
func cm(filePath string, mode string) {
    i, _ := strconv.ParseInt(mode, 8, 32)
    e := os.Chmod(filePath, os.FileMode(i))
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Chmod", e))
        return
    }
}

// 引数で指定したファイルのパーミッションを出力する
func pm(fileName string) {
    fi, e := os.Stat(fileName)
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Stat", e))
        return
    }
    fmt.Printf("[%v] ", fi.Mode().Perm())
}

コードの説明 osパッケージの関数を使ってファイルおよびディレクトリの操作を行っています。 操作が正常に終了した場合は"O"を出力し、エラーが発生した場合は"X"を出力しています。

◆実行結果(Linux)

実行結果の説明 各列ですが、
ls列がリスト取得の可否、cd列が移れるかの可否、
W列、R列、D列が、それぞれ直下のファイルに対して書き込み、読み込み、削除の可否
を表しています。
r権限が、ファイル読み込みではなくリスト取得の可否で、x権限がファイル読み込みの可否だったのが意外でした。 Linuxは細かな制御ができるのが良いですよね。

◆実行結果(Windows)

実行結果の説明 Windowsは、パーミッション(権限)にどんな値を指定しても同じ結果になりました。

最後までご覧いただきありがとうございます

初心者のGo言語 -27- os.Chmodを調べる

こんにちは、kurumi-bioです。

目次

環境

初めに

osパッケージのChmod関数は、二つ目の引数にファイルのパーミッションを渡す仕様になっている。 固定値の場合は8進数の数値を記載することが可能だが、変数で渡すときにフォーマットがわからなかったため、 プログラムを作成して調査することにした。

os.Chmodの説明は下記を参照してください。 kurumi-bio.hatenablog.com

数値とパーミッション(権限)の関係

◆テストコード

package main

import (
    "fmt"
    "os"
)

// 引数で指定したファイルのモードを引数で指定したモードに変更する
func cm(filePath string, mode int) {
    e := os.Chmod(filePath, os.FileMode(mode))
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Chmod", e))
        return
    }
    fmt.Printf("[%02d][%03o]", mode, mode)
}

// 引数で指定したファイルのモードを出力する
func pm(fileName string) {
    fi, e := os.Stat(fileName)
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Stat", e))
        return
    }
    fmt.Printf("[%v]\n", fi.Mode().Perm())
}

func main() {
    const fileName = "test.txt"

    for i := 0; i < 20; i = i + 1 {
        cm(fileName, i)
        pm(fileName)
    }
}

コードの説明 os.Chmod()関数を使って"test.txt"のパーミッション(権限)を変更し、os.Stat関数を使って結果を出力します。 パーミッション(権限)に0~20までの整数を指定し、どのような値になるかを確認します。 実行時の出力は、下記の通りです。

  • 1ブロック目:os.Chmodos.FileModeに渡している数値です。
  • 2ブロック目:os.FileModeに渡している数値を8進数に変換した数値です。
  • 3ブロック目:os.Statで取得したパーミッション(権限)を出力しています。

◆実行結果(Linux)

実行結果の説明 2ブロック目の8進数の値が、Linuxのchmodコマンドの数値で指定した値と同じになっています。 ですので、数値指定の8進数を10進数に変換してos.Chmodに渡せば、 chmodコマンドと同じフォーマットでパーミッション(権限)の変更が可能になります。

8進数文字列でパーミッション(権限)を指定

◆テストコード

package main

import (
    "fmt"
    "os"
    "strconv"
)

// 引数で指定したファイルのモードを引数で指定したモードに変更する
func cm(filePath string, mode string) {
    i, _ := strconv.ParseInt(mode, 8, 32)
    e := os.Chmod(filePath, os.FileMode(i))
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Chmod", e))
        return
    }
    fmt.Printf("[%s][%03d]", mode, i)
}

// 引数で指定したファイルのモードを出力する
func pm(fileName string) {
    fi, e := os.Stat(fileName)
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("os.Stat", e))
        return
    }
    fmt.Printf("[%v]\n", fi.Mode().Perm())
}

func main() {
    const fileName = "test.txt"

    cm(fileName, "0777")
    pm(fileName)

    cm(fileName, "0700")
    pm(fileName)

    cm(fileName, "0060")
    pm(fileName)
}

コードの説明 文字列で記述されたパーミッション(権限)をstrconv.ParseInt関数を使って8進数文字列を整数(10進数)に変換してから、 os.Chmodに渡しています。

  • 1ブロック目:パーミッション(権限)に指定した8進数の文字列です。
  • 2ブロック目:整数(10進数)に変換後の値です。
  • 3ブロック目:os.Statで取得したパーミッション(権限)を出力しています。

◆実行結果(Linux)

実行結果の説明 Linuxのchmodコマンドに渡すフォーマットと同じ記載でパーミッション(権限)が変更できました。

最後までご覧いただきありがとうございます