M.Hiroi's Home Page

Go Language Programming

お気楽 Go 言語プログラミング入門

[ PrevPage | Golang | NextPage ]

簡単な CLI ツールの作成

今回は Go 言語の例題として、テキストを操作する簡単なコマンドを作成してみましょう。Unix 系 OS のコマンドを参考にしていますが、簡易バージョンなので難しい機能は実装していません。それだけプログラムは簡単になります。作成するコマンドは次の通りです。

●cat

//
// cat.go : ファイルの連結
//
//          Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "os"
    "bufio"
    "flag"
)

// オプション
var bflag bool
var nflag bool
var sflag bool

// ファイルの表示
func cat(file *os.File, n int) int {
    s := bufio.NewScanner(file)
    empty := false
    for s.Scan() {
        l := s.Text()
        if l != "" {
            if bflag || nflag {
                fmt.Printf("%6d  %s\n", n, l)
                n++
            } else {
                fmt.Println(l)
            }
            empty = false
        } else {
            if sflag && empty { continue }
            if nflag {
                fmt.Printf("%6d  \n", n)
                n++
            } else {
                fmt.Println("")
            }
            empty = true
        }
    }
    return n
}

func main() {
    // フラグの設定
    flag.BoolVar(&bflag, "b", false, "空行以外に番号を付ける")
    flag.BoolVar(&nflag, "n", false, "行に番号を付ける")
    flag.BoolVar(&sflag, "s", false, "連続した空行を一つにまとめる")
    flag.Parse()
    if bflag && nflag {
        nflag = false
    }
    // 行数
    n := 1
    if flag.NArg() == 0 {
        // 標準入力
        cat(os.Stdin, n)
    } else {
        for _, name := range flag.Args() {
            file, err := os.Open(name)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }
            n = cat(file, n)
            file.Close()
        }
    }
}
$ ./cat test01.txt
abcd

ABCD
efghi


EFGHI
end of file
$ ./cat -n test01.txt
     1  abcd
     2
     3  ABCD
     4  efghi
     5
     6
     7  EFGHI
     8  end of file
$ ./cat -b test01.txt
     1  abcd

     2  ABCD
     3  efghi


     4  EFGHI
     5  end of file
$ ./cat -b -s test01.txt
     1  abcd

     2  ABCD
     3  efghi

     4  EFGHI
     5  end of file

●cut

//
// cut.go : 行から n 番目のフィールドを切り出す
//
//          Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "bufio"
    "os"
    "strings"
    "strconv"
    "flag"
)

// フラグ
var dflag string
var fflag string
var oflag string

func cut(file *os.File, fn []int) {
    s := bufio.NewScanner(file)
    for s.Scan() {
        l := s.Text()
        ss := strings.Split(l, dflag)
        for i, n := range fn {
            if 1 <= n && n <= len(ss) {
                fmt.Printf("%s", ss[n - 1])
            } else {
                fmt.Print("")
            }
            if i < len(fn) - 1 {
                fmt.Print(oflag)
            }
        }
        fmt.Println("")
    }
}

func main() {
    // フラグの設定
    flag.StringVar(&dflag, "d", "\t", "区切り文字を指定する")
    flag.StringVar(&fflag, "f", "", "フィールド番号を指定する")
    flag.StringVar(&oflag, "o", "", "出力時の区切り文字を指定する")
    flag.Parse()
    if fflag == "" {
        fmt.Fprintln(os.Stderr, "フィールド番号がありません")
        os.Exit(1)
    }
    if oflag == "" {
        oflag = dflag
    }
    fs := strings.Split(fflag, ",")
    fn := make([]int, len(fs))
    for i, s := range fs {
        n, err := strconv.Atoi(s)
        if err != nil || n <= 0 {
            fmt.Fprintf(os.Stderr, "-f には 1 以上の数値を指定してください: %v\n", err)
            os.Exit(1)
        }
        fn[i] = n
    }
    if flag.NArg() == 0 {
        // 標準入力
        cut(os.Stdin, fn)
    } else {
        for _, name := range flag.Args() {
            file, err := os.Open(name)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }
            cut(file, fn)
            file.Close()
        }
    }
}
$ cat test02.txt
abcd    1234    ABCD
efghi   56789   EFGHI
jkl     987     JKL
mnopqrs 6543210 MNOPQRS
$ ./cut -f 1 test02.txt
abcd
efghi
jkl
mnopqrs
$ ./cut -f 3 test02.txt
ABCD
EFGHI
JKL
MNOPQRS
$ ./cut -f 2,3 test02.txt
1234    ABCD
56789   EFGHI
987     JKL
6543210 MNOPQRS
$ ./cut -o , -f 2,3 test02.txt
1234,ABCD
56789,EFGHI
987,JKL
6543210,MNOPQRS

●paste

//
// paste.go : 行単位でファイルを連結する
//
//            Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "os"
    "bufio"
    "flag"
)

// オプション
var dflag string

// 行の連結
func paste(files []*bufio.Scanner) {
    buff := make([]string, len(files))
    for {
        n := 0
        for i, s := range files {
            if s.Scan() {
                buff[i] = s.Text()
                n++
            } else {
                buff[i] = ""
            }
        }
        if n == 0 { break }
        for i, l := range buff {
            if i == len(buff) - 1 {
                fmt.Printf("%s\n", l)
            } else {
                fmt.Printf("%s%s", l, dflag)
            }
        }
    }
}

func main() {
    // フラグの設定
    flag.StringVar(&dflag, "d", "\t", "区切り文字の指定")
    flag.Parse()
    if flag.NArg() == 0 {
        // 標準入力
        ss := []*bufio.Scanner {bufio.NewScanner(os.Stdin)}
        paste(ss)
    } else {
        files := make([]*os.File, flag.NArg())
        ss := make([]*bufio.Scanner, flag.NArg())
        for i, name := range flag.Args() {
            file, err := os.Open(name)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }
            files[i] = file
            ss[i] = bufio.NewScanner(file)
        }
        paste(ss)
        for _, file := range files {
            file.Close()
        }
    }
}
$ ./cut -f 1 test02.txt > test03.txt
$ ./cut -f 2 test02.txt > test04.txt
$ ./cut -f 3 test02.txt > test05.txt
$ paste test03.txt test04.txt test05.txt
abcd    1234    ABCD
efghi   56789   EFGHI
jkl     987     JKL
mnopqrs 6543210 MNOPQRS
$ paste -d , test03.txt test04.txt test05.txt
abcd,1234,ABCD
efghi,56789,EFGHI
jkl,987,JKL
mnopqrs,6543210,MNOPQRS

●head

//
// haed.go : ファイルの先頭を表示する
//
//           Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "os"
    "bufio"
    "flag"
)

func head(file *os.File, n uint) {
    s := bufio.NewScanner(file)
    var i uint
    for i = 0; i < n; i++ {
        if !s.Scan() { break }
        fmt.Println(s.Text())
    }
}

func main() {
    // フラグの設定
    nflag := flag.Uint("n", 10, "ファイルの先頭 n 行表示する")
    qflag := flag.Bool("q", false, "ファイルヘッダを表示しない")
    vflag := flag.Bool("v", false, "ファイルヘッダを常に表示する")
    flag.Parse()
    if flag.NArg() == 0 {
        // 標準入力
        if *vflag {
            fmt.Println("==>標準入力<==")
        }
        head(os.Stdin, *nflag)
    } else {
        for i, name := range flag.Args() {
            file, err := os.Open(name)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }
            if !(*qflag) && (*vflag || flag.NArg() > 1) {
                fmt.Printf("==>%s<==\n", name)
            }
            head(file, *nflag)
            if !(*qflag) && i < flag.NArg() - 1 {
                fmt.Println("")
            }
            file.Close()
        }
    }
}
$ ./head -n 3 test03.txt
abcd
efghi
jkl
$ ./head -n 3 test03.txt test04.txt test05.txt
==>test03.txt<==
abcd
efghi
jkl

==>test04.txt<==
1234
56789
987

==>test05.txt<==
ABCD
EFGHI
JKL
$ ./head -q -n 3 test03.txt test04.txt test05.txt
abcd
efghi
jkl
1234
56789
987
ABCD
EFGHI
JKL

●tail

//
// tail.go : ファイルの末尾を表示する
//
//           Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "os"
    "bufio"
    "flag"
)

func tail(file *os.File, n uint) {
    s := bufio.NewScanner(file)
    queue := make([]string, n)
    var r, w, c uint = 0, 0, 0
    for s.Scan() {
        l := s.Text()
        if c >= n {
            c--
            r++
            if r >= n { r = 0 }
        }
        queue[w] = l
        c++
        w++
        if w >= n { w = 0 }
    }
    // 表示
    for ; c > 0; c-- {
        fmt.Println(queue[r])
        r++
        if r >= n { r = 0 }
    }
}

func main() {
    // フラグの設定
    nflag := flag.Uint("n", 10, "ファイルの末尾 n 行表示する")
    qflag := flag.Bool("q", false, "ファイルヘッダを表示しない")
    vflag := flag.Bool("v", false, "ファイルヘッダを常に表示する")
    flag.Parse()
    if flag.NArg() == 0 {
        // 標準入力
        if *vflag {
            fmt.Println("==>Stdin<==")
        }
        tail(os.Stdin, *nflag)
    } else {
        for i, name := range flag.Args() {
            file, err := os.Open(name)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }
            if !(*qflag) && (*vflag || flag.NArg() > 1) {
                fmt.Printf("==>%s<==\n", name)
            }
            tail(file, *nflag)
            if !(*qflag) && i < flag.NArg() - 1 {
                fmt.Println("")
            }
            file.Close()
        }
    }
}
$ ./tail -n 3 test03.txt
efghi
jkl
mnopqrs
$ ./tail -n 3 test03.txt test04.txt test05.txt
==>test03.txt<==
efghi
jkl
mnopqrs

==>test04.txt<==
56789
987
6543210

==>test05.txt<==
EFGHI
JKL
MNOPQRS
$ ./tail -q -n 3 test03.txt test04.txt test05.txt
efghi
jkl
mnopqrs
56789
987
6543210
EFGHI
JKL
MNOPQRS

●wc

//
// wc.go : 単語のカウント
//
//         Copyright (C) 2022 Makoto Hiori
//
package main

import (
    "os"
    "io"
    "bufio"
    "fmt"
    "flag"
    "unicode"
)

// フラグ
var cflag bool
var mflag bool
var lflag bool
var wflag bool
var nflag int

func wc(file *os.File) (int, int, int, int) {
    rd := bufio.NewReader(file)
    c, l, w, m := 0, 0, 0, 0    // バイト数, 行数, 単語数, 文字数
    inword  := false
    for {
        r, s, err := rd.ReadRune()
        if s == 0 {
            if err == io.EOF {
                return c, l, w, m
            } else {
                fmt.Fprintf(os.Stderr, "%d バイト目で不正なコードを検出しました\n", c + 1)
                os.Exit(1)
            }
        } else if unicode.IsSpace(r) {
            inword = false
            if r == '\n' { l++ }
        } else if !inword {
            inword = true
            w++
        }
        c += s
        m++
    }
}

func printCount(c, l, w, m int, name string) {
    if lflag {
        fmt.Printf("%[1]*[2]d ", nflag, l)
    }
    if wflag {
        fmt.Printf("%[1]*[2]d ", nflag, w)
    }
    if mflag {
        fmt.Printf("%[1]*[2]d ", nflag, m)
    }
    if cflag {
        fmt.Printf("%[1]*[2]d ", nflag, c)
    }
    if !lflag && !wflag && !cflag && !mflag {
        fmt.Printf("%[1]*[2]d %[1]*[3]d %[1]*[4]d ", nflag, l, w, c)
    }
    fmt.Println(name)
}

func main() {
    // フラグの設定
    flag.BoolVar(&cflag, "c", false, "バイト数を表示する")
    flag.BoolVar(&mflag, "m", false, "文字数を表示する")
    flag.BoolVar(&lflag, "l", false, "行数を表示する")
    flag.BoolVar(&wflag, "w", false, "単語数を表示する")
    flag.IntVar(&nflag, "n", 6, "数のフィールド幅を指定する")
    flag.Parse()
    if flag.NArg() == 0 {
        // 標準入力
        c, l, w, m := wc(os.Stdin)
        printCount(c, l, w, m, "")
    } else {
        ca, la, wa, ma := 0, 0, 0, 0    // 合計値
        for _, name := range flag.Args() {
            file, err := os.Open(name)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }
            c, l, w, m := wc(file)
            ca += c
            la += l
            wa += w
            ma += m
            printCount(c, l, w, m, name)
            file.Close()
        }
        if flag.NArg() > 1 {
            printCount(ca, la, wa, ma, "合計")
        }
    }
}
$ ./wc wc.go
   101    310   2280 wc.go
$ ./wc -m wc.go
  2080 wc.go
$ ./wc -c -m wc.go
  2080   2280 wc.go
$ ./wc -l -w -c -m wc.go
   101    310   2080   2280 wc.go
$ ./wc -l -w -c -m wc.go head.go tail.go
   101    310   2080   2280 wc.go
    54    140   1079   1209 head.go
    69    193   1299   1425 tail.go
   224    643   4458   4914 合計

●grep

//
// grep.go : 正規表現による文字列の検索
//
//           Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "flag"
    "bufio"
    "os"
    "regexp"
)

// オプション
var cflag bool
var qflag bool
var iflag bool
var nflag bool
var vflag bool

func printLine(l, name string, n int) {
    if !qflag && name != "" {
        fmt.Printf("%s:", name)
    }
    if nflag {
        fmt.Printf("%d:", n)
    }
    fmt.Println(l)
}

func grep(file *os.File, re *regexp.Regexp, name string) {
    s := bufio.NewScanner(file)
    n := 1
    c := 0
    for s.Scan() {
        l := s.Text()
        if re.MatchString(l) {
            c++
            if !vflag && !cflag{
                printLine(l, name, n)
            }
        } else if vflag && !cflag {
            printLine(l, name, n)
        }
        n++
    }
    if cflag {
        if !qflag && name != "" {
            fmt.Printf("%s:", name)
        }
        fmt.Println(c)
    }
}

func main() {
    // オプションの設定
    flag.BoolVar(&cflag, "c", false, "パターンと一致した行数を求める")
    flag.BoolVar(&qflag, "q", false, "ファイル名を表示しない")
    flag.BoolVar(&iflag, "i", false, "英大小文字を区別しない")
    flag.BoolVar(&nflag, "n", false, "行数を表示する")
    flag.BoolVar(&vflag, "v", false, "パターンと一致しない行を表示する")
    flag.Parse()

    // 検索パターンのコンパイル
    if flag.NArg() == 0 {
        fmt.Fprintln(os.Stderr, "検索パターンが必要です")
        os.Exit(1)
    }
    args := flag.Args()
    pattern := args[0]
    if iflag {
        pattern = `(?i)` + pattern
    }
    re, err := regexp.Compile(pattern)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }

    // 入力ファイルの取得
    if flag.NArg() == 1 {
        grep(os.Stdin, re, "")
    } else {
        for _, name := range args[1:] {
            file, err := os.Open(name)
            if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
            }
            if flag.NArg() == 2 {
                grep(file, re, "")
            } else {
                grep(file, re, name)
            }
            file.Close()
        }
    }
}
$ ./grep '[a-d]+' test02.txt test03.txt test05.txt
test02.txt:abcd 1234    ABCD
test03.txt:abcd
$ ./grep -n '[a-d]+' test02.txt test03.txt test05.txt
test02.txt:1:abcd       1234    ABCD
test03.txt:1:abcd
$ ./grep -n -i '[a-d]+' test02.txt test03.txt test05.txt
test02.txt:1:abcd       1234    ABCD
test03.txt:1:abcd
test05.txt:1:ABCD
$ ./grep -v -n -i '[a-d]+' test02.txt test03.txt test05.txt
test02.txt:2:efghi      56789   EFGHI
test02.txt:3:jkl        987     JKL
test02.txt:4:mnopqrs    6543210 MNOPQRS
test03.txt:2:efghi
test03.txt:3:jkl
test03.txt:4:mnopqrs
test05.txt:2:EFGHI
test05.txt:3:JKL
test05.txt:4:MNOPQRS
$ ./grep -c '[a-d]+' test02.txt test03.txt test05.txt
test02.txt:1
test03.txt:1
test05.txt:0
$ ./grep -c -i '[a-d]+' test02.txt test03.txt test05.txt
test02.txt:1
test03.txt:1
test05.txt:1

●gres

//
// gres.go : 正規表現による文字列の置換
//
//           Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "flag"
    "bufio"
    "os"
    "regexp"
)

// オプション
var iflag bool

func main() {
    // オプションの設定
    flag.BoolVar(&iflag, "i", false, "英大小文字を区別しない")
    flag.Parse()

    // 検索パターンのコンパイル
    if flag.NArg() < 2 {
        fmt.Fprintln(os.Stderr, "検索パターンと置換パターンが必要です")
        os.Exit(1)
    }
    args := flag.Args()
    pattern := args[0]
    repl := args[1]
    if iflag {
        pattern = `(?i)` + pattern
    }
    re, err := regexp.Compile(pattern)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }

    // 置換
    s := bufio.NewScanner(os.Stdin)
    for s.Scan() {
        fmt.Println(re.ReplaceAllString(s.Text(), repl))
    }
}
$ ./gres '[a-z]+' '<$0>' < test02.txt
<abcd>  1234    ABCD
<efghi> 56789   EFGHI
<jkl>   987     JKL
<mnopqrs>       6543210 MNOPQRS
$ ./gres -i '[a-z]+' '<$0>' < test02.txt
<abcd>  1234    <ABCD>
<efghi> 56789   <EFGHI>
<jkl>   987     <JKL>
<mnopqrs>       6543210 <MNOPQRS>
$ ./gres -i $'\t' ',' < test02.txt
abcd,1234,ABCD
efghi,56789,EFGHI
jkl,987,JKL
mnopqrs,6543210,MNOPQRS
$ ./gres -i $'\t' '' < test02.txt
abcd1234ABCD
efghi56789EFGHI
jkl987JKL
mnopqrs6543210MNOPQRS

●sort

//
// sort.go : テキストファイルのソート
//
//           Copyright (C) 2022 Makoto Hiroi
//
package main

import (
    "fmt"
    "flag"
    "os"
    "bufio"
    "strings"
    "unicode"
    "sort"
)

// フラグ
var kflag int      // フィールド指定 (0 は行全体とする)
var bflag bool     // 先頭の空白文字を無視する
var iflag bool     // 英大小文字を区別しない
var nflag bool     // 文字列を数値としてソートする
var rflag bool     // 降順にソート
var tflag string   // デリミタ (デフォルトは空白文字)

// 文字列のソート
func strSort(file *os.File) {
    s := bufio.NewScanner(file)
    buff := make([][]string, 0)
    n := 0
    for s.Scan() {
        var k string
        l := s.Text()
        xs := strings.Split(l, tflag)
        if len(xs) < kflag {
            fmt.Fprintf(os.Stderr, "%d: %s, フィールド %d がありません\n", n + 1, l, kflag)
            os.Exit(1)
        }
        if kflag == 0 {
            k = l
        } else {
            k = xs[kflag - 1]
        }
        if bflag {
            k = strings.TrimLeftFunc(k, unicode.IsSpace)
        }
        if iflag {
            k = strings.ToLower(k)
        }
        buff = append(buff, []string {l, k})
        n++
    }
    if rflag {
        sort.SliceStable(buff, func(i, j int) bool { return buff[i][1] > buff[j][1] })
    } else {
        sort.SliceStable(buff, func(i, j int) bool { return buff[i][1] < buff[j][1] })
    }
    for _, ls := range buff {
        fmt.Println(ls[0])
    }
}

// 数値のソート
func strToNum(s string, l int) int {
    var n int
    _, err := fmt.Sscanf(s, "%d", &n)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%d: %s, %v\n", l + 1, s, err)
        os.Exit(1)
    }
    return n
}

func numSort(file *os.File) {
    s := bufio.NewScanner(file)
    strBuff := make([]string, 0)
    numBuff := make([][]int, 0)
    n := 0
    for s.Scan() {
        var k int
        l := s.Text()
        xs := strings.Split(l, tflag)
        if len(xs) < kflag {
            fmt.Fprintf(os.Stderr, "%d: %s, フィールド %d がありません\n", n + 1, l, kflag)
            os.Exit(1)
        }
        if kflag == 0 {
            k = strToNum(l, n)
        } else {
            k = strToNum(xs[kflag - 1], n)
        }
        strBuff = append(strBuff, l)
        numBuff = append(numBuff, []int {n, k})
        n++
    }
    if rflag {
        sort.SliceStable(numBuff, func(i, j int) bool { return numBuff[i][1] > numBuff[j][1] })
    } else {
        sort.SliceStable(numBuff, func(i, j int) bool { return numBuff[i][1] < numBuff[j][1] })
    }
    for _, ls := range numBuff {
        fmt.Println(strBuff[ls[0]] )
    }
}

func main() {
    // フラグの設定
    flag.IntVar(&kflag, "k", 0, "フィールドを指定する")
    flag.StringVar(&tflag, "t", "\t", "区切り文字を指定する")
    flag.BoolVar(&bflag, "b", false, "先頭の空白文字を無視する")
    flag.BoolVar(&iflag, "i", false, "英大小文字を区別しない")
    flag.BoolVar(&nflag, "n", false, "文字列を数値としてソートする")
    flag.BoolVar(&rflag, "r", false, "降順にソートする")
    flag.Parse()
    if flag.NArg() == 0 {
        // 標準入力
        if nflag {
            numSort(os.Stdin)
        } else {
            strSort(os.Stdin)
        }
    } else {
        file, err := os.Open(flag.Args()[0])
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
        if nflag {
            numSort(file)
        } else {
            strSort(file)
        }
        file.Close()
    }
}
$ ./cat test06.txt
abcd    1234    ABCD
efghi   56789   EFGHI
jkl     987     JKL
mnopqrs 6543210 MNOPQRS
DCBA    4321    abcd
IHGFE   98765   efghi
LKJ     789     jkl
SRQPONM 123456  mnopqrs
$ ./sort < test06.txt
DCBA    4321    abcd
IHGFE   98765   efghi
LKJ     789     jkl
SRQPONM 123456  mnopqrs
abcd    1234    ABCD
efghi   56789   EFGHI
jkl     987     JKL
mnopqrs 6543210 MNOPQRS
$ ./sort -k 2 test06.txt
abcd    1234    ABCD
SRQPONM 123456  mnopqrs
DCBA    4321    abcd
efghi   56789   EFGHI
mnopqrs 6543210 MNOPQRS
LKJ     789     jkl
jkl     987     JKL
IHGFE   98765   efghi
$ ./sort -k 3 test06.txt
abcd    1234    ABCD
efghi   56789   EFGHI
jkl     987     JKL
mnopqrs 6543210 MNOPQRS
DCBA    4321    abcd
IHGFE   98765   efghi
LKJ     789     jkl
SRQPONM 123456  mnopqrs
$ ./sort -i -k 3 test06.txt
abcd    1234    ABCD
DCBA    4321    abcd
efghi   56789   EFGHI
IHGFE   98765   efghi
jkl     987     JKL
LKJ     789     jkl
mnopqrs 6543210 MNOPQRS
SRQPONM 123456  mnopqrs
$ ./sort -n -k 2 test06.txt
LKJ     789     jkl
jkl     987     JKL
abcd    1234    ABCD
DCBA    4321    abcd
efghi   56789   EFGHI
IHGFE   98765   efghi
SRQPONM 123456  mnopqrs
mnopqrs 6543210 MNOPQRS

Copyright (C) 2022 Makoto Hiroi
All rights reserved.

[ PrevPage | Golang | NextPage ]