golang 命令行解析庫cobra的使用

golang 命令行解析庫cobra的使用

? 關(guān)于go語言的命令行解析,標(biāo)準(zhǔn)庫flag提供的功能比較少,不能滿足我的使用需求,所以就需要尋找第三方解決方案了.我選擇cobra是因為它支持 sub command子命令,滿足我的使用需求,而且被很多大公司使用(比如github cli),安全性應(yīng)該沒什么問題.

Overview

cobra提供簡單的接口用于創(chuàng)建類似于git或者go官方工具的命令行工具

cobra可以快速創(chuàng)建基于cobra的應(yīng)用的腳手架.

  • 簡單的基于子命令的命令行接口: app server, app fetch等等
  • 完全兼容POSIX的flag(包括短和長版本)
  • 嵌套子命令
  • 全局,本地和級聯(lián)flag
  • 快速生成應(yīng)用腳手架cobra init appname & cobra add cmdname
  • 智能提示功能 (app srver... did you mean app server?)
  • 自動基于command和flag生成幫助
  • 自動添加幫助flag -h, --help
  • 自動添加shell自動補(bǔ)全功能(bash, zsh, fish, powershell)
  • 自動生成幫助文檔
  • command 別名功能
  • 靈活定制help,usage等等
  • 可選 集成viper用于構(gòu)建12-factor app

Concept

cobra基于結(jié)構(gòu)化的command,arguments和flag進(jìn)行構(gòu)建

Commands代表行動

Args表示事物

Flags 是行動的修飾符

最好的應(yīng)用使用起來像讀語句一樣,用戶會自然而然地知道如何使用這個應(yīng)用

遵循的原則是

APPNAME VERB NOUN --ADJECTIVE.或者APPNAME COMMAND ARG --FLAG

在接下來的例子里, server是一個command,port是一個flag

hugo server --port=1313

下一個命令我們在告訴git,從目標(biāo)url 克隆一個裸倉庫(只拷貝.git子目錄)

git clone URL --bare

Commands

command是應(yīng)用的核心,每次應(yīng)用的互動都會包含一個command,一個command可以存在可選的子command,在這個例子hugo server --port=1313里,server就是一個command

More about cobra.Command

Flags

flag是修改command行為的一種方式,cobra提供完全POSIX兼容的flag就像gp官方命令行工具一樣.

cobra的command可以定義影響所有子命令的flag或者只影響一個命令的command

.在這個例子里,hugo server --port=1313 port是一個flag

flag功能由pflag庫提供.

Installing

使用go get下載最新版

go get -u github.com/spf13/cobra

導(dǎo)入項目:

import "github.com/spf13/cobra"

Getting Started

典型的項目結(jié)構(gòu)

  ? appName/
    ? cmd/
        add.go
        your.go
        commands.go
        here.go
      main.go

通常main行數(shù)比較簡單,起到初始化cobra的作用

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

Using the Cobra Generator

cobra自身提供的程序可以幫助你創(chuàng)建app和添加command,這是最簡單的添加cobra到你的應(yīng)用程序的方式

Here you can find more information about it.

Using the Cobra Library

手動實現(xiàn)cobra,你需要創(chuàng)建一個main.go和rootCmd文件,你需要視情況添加額外的command

Create rootCmd

cobra不需要構(gòu)造函數(shù),簡單地創(chuàng)建你的command即可.

通常把rootCmd放到 app/cmd/root.go下:

var rootCmd = &cobra.Command{
  Use:   "hugo",
  Short: "Hugo is a very fast static site generator",
  Long: `A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at http://hugo.spf13.com`,
  Run: func(cmd *cobra.Command, args []string) {
    // Do Stuff Here
  },
}

func Execute() {
  if err := rootCmd.Execute(); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }
}

可以在init函數(shù)中定義flag或者修改配置

For example cmd/root.go:

package cmd

import (
    "fmt"
    "os"

    homedir "github.com/mitchellh/go-homedir"
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var (
    // Used for flags.
    cfgFile     string
    userLicense string

    rootCmd = &cobra.Command{
        Use:   "cobra",
        Short: "A generator for Cobra based Applications",
        Long: `Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
    }
)

// Execute executes the root command.
func Execute() error {
    return rootCmd.Execute()
}

func init() {
    cobra.OnInitialize(initConfig)

    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
    rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
    rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
    rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
    viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
    viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
    viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
    viper.SetDefault("license", "apache")

    rootCmd.AddCommand(addCmd)
    rootCmd.AddCommand(initCmd)
}

func er(msg interface{}) {
    fmt.Println("Error:", msg)
    os.Exit(1)
}

func initConfig() {
    if cfgFile != "" {
        // Use config file from the flag.
        viper.SetConfigFile(cfgFile)
    } else {
        // Find home directory.
        home, err := homedir.Dir()
        if err != nil {
            er(err)
        }

        // Search config in home directory with name ".cobra" (without extension).
        viper.AddConfigPath(home)
        viper.SetConfigName(".cobra")
    }

    viper.AutomaticEnv()

    if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Using config file:", viper.ConfigFileUsed())
    }
}

Create your main.go

定義好root command后,你需要在main函數(shù)里執(zhí)行execute

在cobra app中,通常main.go文件中的內(nèi)容比較少,只用于初始化cobra

In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra.

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

Create additional commands

可以添加新的command,通常我們把每個command放到一個文件里,放在cmd/目錄下

如果你想添加一個version command,你可以創(chuàng)建cmd/version.go,然后像下面這個例子一樣填充內(nèi)容

package cmd

import (
  "fmt"

  "github.com/spf13/cobra"
)

func init() {
  rootCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
  Use:   "version",
  Short: "Print the version number of Hugo",
  Long:  `All software has versions. This is Hugo's`,
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
  },
}

Returning and handling errors

If you wish to return an error to the caller of a command, RunE can be used.

如果你想針對command的調(diào)用返回一個error,可以使用RunE

package cmd

import (
  "fmt"

  "github.com/spf13/cobra"
)

func init() {
  rootCmd.AddCommand(tryCmd)
}

var tryCmd = &cobra.Command{
  Use:   "try",
  Short: "Try and possibly fail at something",
  RunE: func(cmd *cobra.Command, args []string) error {
    if err := someFunc(); err != nil {
    return err
    }
    return nil
  },
}

這樣在execute函數(shù)調(diào)用的時候,error就會被捕獲

Working with Flags

通過flag傳入?yún)?shù)可以控制comand的執(zhí)行行為

Assign flags to a command

由于flag定義后,可以在很多不同的地方被使用,我們需要定義一個變量來接受flag

var Verbose bool
var Source string

Persistent Flags

persistent(持久性) flag,可以用于分配給他的命令以及所有子命令.

下面例子里的verbose作用是讓命令行輸出的信息更詳細(xì),所有的子命令都會支持 verbos flag

rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

Local Flags

local flag ,本地分配,只會分配給那個特定的command

localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

Local Flag on Parent Commands

默認(rèn)情況下 cobra只會在目標(biāo)command的基礎(chǔ)上解析local flag,它的parent command會忽略解析.開啟Command.TraverseChildren選項后,cobra就會執(zhí)行目標(biāo)command之前,在每個command上解析local command

command := cobra.Command{
  Use: "print [OPTIONS] [COMMANDS]",
  TraverseChildren: true,
}

Bind Flags with Config

你也可以使用viper綁定flag

var author string

func init() {
  rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
  viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}

在這個persistent flag的例子中persistent flag author ,用viper進(jìn)行了綁定, 注意,當(dāng)用戶沒有使用 --author flag時,就不會從配置文件中讀取值

More in viper documentation.

Required flags

Flags are optional by default. If instead you wish your command to report an error when a flag has not been set, mark it as required:

默認(rèn)情況下flag時可選的,如果你希望缺失flag的時候報錯,可以標(biāo)記為Required flags,如下

rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")

或者對于PersistentFlag來說

rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkPersistentFlagRequired("region")

Positional and Custom Arguments

位置參數(shù)的驗證,可以使用commnad的 args字段

下列驗證器是內(nèi)置的:

  • NoArgs - 如果沒有任何參數(shù),command會報錯
  • ArbitraryArgs - command接受任何參數(shù)
  • OnlyValidArgs - 如果有任何位置參數(shù)不在 validArgs的范圍里面, command會報錯
  • MinimumNArgs(int) - 當(dāng)位置參數(shù)的個數(shù)少于n時,command 會報錯
  • MaximumNArgs(int) - 當(dāng)位置參數(shù)的個數(shù)大于n時,,command會報錯
  • ExactArgs(int) - 當(dāng)位置參數(shù)的個數(shù)不正好是n個時,command會報錯
  • ExactValidArgs(int) - 當(dāng)不是正好有n個位置參數(shù)或者有任何位置參數(shù)不屬于ValidArgs,command會報錯.
  • RangeArgs(min, max) - 如果參數(shù)的個數(shù)不是在min和max之間,command會報錯

關(guān)于自定義驗證器的一個例子:

var cmd = &cobra.Command{
  Short: "hello",
  Args: func(cmd *cobra.Command, args []string) error {
    if len(args) < 1 {
      return errors.New("requires a color argument")
    }
    if myapp.IsValidColor(args[0]) {
      return nil
    }
    return fmt.Errorf("invalid color specified: %s", args[0])
  },
  Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("Hello, World!")
  },
}

Example

在下面的例子里,定義了3個command,其中有兩個時最高級別的command,另一個(cmdTimes)時其中一個的子command.在這個例子里,root不能執(zhí)行意味著需要subcommand,這是通過不給rootCmd提供Run參數(shù)來實現(xiàn)的.

More documentation about flags is available at https://github.com/spf13/pflag

執(zhí)行go run main.go echo dasda dasdaa dadsa, 執(zhí)行go run main.go print dasada dasdafesf fasfdasf

times是echo的子命令所以執(zhí)行命令是這樣的go run main.go echo times -t 10 dsadas dasda dasda

package main

import (
  "fmt"
  "strings"

  "github.com/spf13/cobra"
)

func main() {
  var echoTimes int

  var cmdPrint = &cobra.Command{
    Use:   "print [string to print]",
    Short: "Print anything to the screen",
    Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
    Args: cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Println("Print: " + strings.Join(args, " "))
    },
  }

  var cmdEcho = &cobra.Command{
    Use:   "echo [string to echo]",
    Short: "Echo anything to the screen",
    Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
    Args: cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Println("Echo: " + strings.Join(args, " "))
    },
  }

  var cmdTimes = &cobra.Command{
    Use:   "times [string to echo]",
    Short: "Echo anything to the screen more times",
    Long: `echo things multiple times back to the user by providing
a count and a string.`,
    Args: cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
      for i := 0; i < echoTimes; i++ {
        fmt.Println("Echo: " + strings.Join(args, " "))
      }
    },
  }

  cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")

  var rootCmd = &cobra.Command{Use: "app"}
  rootCmd.AddCommand(cmdPrint, cmdEcho)
  cmdEcho.AddCommand(cmdTimes)
  rootCmd.Execute()
}

For a more complete example of a larger application, please checkout Hugo.

Help Command

當(dāng)你有一個新的subcommand,cobra會自動為你的應(yīng)用添加help command.當(dāng)用戶執(zhí)行app help時,會調(diào)用默認(rèn)的help command.并且help支持所有其他輸入的command.每個command都會自動添加--helpflag

Example

下面的輸出是由cobra自動生成的. 除了command和flag你不需要其他東西就能生成help command

$ cobra help

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

Usage:
  cobra [command]

Available Commands:
  add         Add a command to a Cobra Application
  help        Help about any command
  init        Initialize a Cobra Application

Flags:
  -a, --author string    author name for copyright attribution (default "YOUR NAME")
      --config string    config file (default is $HOME/.cobra.yaml)
  -h, --help             help for cobra
  -l, --license string   name of license for the project
      --viper            use Viper for configuration (default true)

Use "cobra [command] --help" for more information about a command.

help就和其他command一樣,沒有特殊的邏輯或行為.如果你想的話也可以自定義help.

Defining your own help

你可以提供自己的和help command或者你自己的模板,只需要使用下面的函數(shù)

cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)

The latter two will also apply to any children commands.

后兩個,還可以提供給子command

Usage Message

當(dāng)用戶提供了一個無效的flag或者command,cobra會回復(fù)一份usage

Example

默認(rèn)的help,也嵌入了一份usage作為輸出

下面是遇到無效flag,輸出usage文檔的例子

$ cobra --invalid
Error: unknown flag: --invalid
Usage:
  cobra [command]

Available Commands:
  add         Add a command to a Cobra Application
  help        Help about any command
  init        Initialize a Cobra Application

Flags:
  -a, --author string    author name for copyright attribution (default "YOUR NAME")
      --config string    config file (default is $HOME/.cobra.yaml)
  -h, --help             help for cobra
  -l, --license string   name of license for the project
      --viper            use Viper for configuration (default true)

Use "cobra [command] --help" for more information about a command.

Defining your own usage

你可以提供自己的usage函數(shù)或者模板,這樣就會覆蓋公用模板了.

cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)

Version Flag

如果你的root command上有一個version字段,cobra會添加一個--versionflag. 使用--versionflag會使用version模板打印版本到輸出.模板可以通過cmd.SetVersionTemplate(s string)函數(shù)來自定義

PreRun and PostRun Hooks

在你main函數(shù)里的run函數(shù)運(yùn)行之前和之后,也是可以運(yùn)行函數(shù)的.PersistentPreRunPreRun 函數(shù)會在Run之前執(zhí)行,PersistentPostRunPostRun會在Run之后執(zhí)行. 符合Persistent*Run格式的函數(shù)如果子命令沒有定義自己的Run,就會繼承.這些函數(shù)的執(zhí)行順序如下:

  • PersistentPreRun
  • PreRun
  • Run
  • PostRun
  • PersistentPostRun

下面是一個兩個comand運(yùn)用了以上所有feature的例子.當(dāng)子command執(zhí)行的時候,會執(zhí)行root command的PersistentPreRun函數(shù)而不是root command的PersistentPostRun函數(shù).

package main

import (
  "fmt"

  "github.com/spf13/cobra"
)

func main() {

  var rootCmd = &cobra.Command{
    Use:   "root [sub]",
    Short: "My root command",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
    },
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd Run with args: %v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
    },
  }

  var subCmd = &cobra.Command{
    Use:   "sub [no options!]",
    Short: "My subcommand",
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd Run with args: %v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
    },
  }

  rootCmd.AddCommand(subCmd)

  rootCmd.SetArgs([]string{""})
  rootCmd.Execute()
  fmt.Println()
  rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
  rootCmd.Execute()
}

Output:

Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Inside rootCmd Run with args: []
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []

Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
Inside subCmd Run with args: [arg1 arg2]
Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2]

Suggestions when "unknown command" happens

當(dāng)unknow command錯誤發(fā)生時,cobra會自動打印建議.這使得cobra擁有和git命令行類似的效果

$ hugo srever
Error: unknown command "srever" for "hugo"

Did you mean this?
        server

Run 'hugo --help' for usage.

建議會基于每個子command的注冊自動生成,實現(xiàn)基于 Levenshtein distance.每個注冊的command匹配到最小兩個字母的時候,就會顯示建議

如果你想要取消建議功能,或者調(diào)整字符的距離:

command.DisableSuggestions = true

or

command.SuggestionsMinimumDistance = 1

你也可以設(shè)置可能會被建議的command的名字,使用SuggestFor屬性.這對那些字符串距離不太近的單詞可以起到效果,但是注意不要使用那些你會用來作為別名的名字

$ kubectl remove
Error: unknown command "remove" for "kubectl"

Did you mean this?
        delete

Run 'kubectl help' for usage.

Generating documentation for your command

cobra會基于你的 subcommand和flag生成文檔.Read more about it in the docs generation documentation.

Generating shell completions

cobra能生成shell自動補(bǔ)全,Bash, Zsh, Fish, Powershell.Read more about it in Shell Completions.

關(guān)于cobra generator的使用

cobra用于創(chuàng)建命令行應(yīng)用的腳手架工具

執(zhí)行下面的命令安裝

go get github.com/spf13/cobra/cobra

cobra init

創(chuàng)建應(yīng)用初始代碼,提供正確的項目結(jié)構(gòu),并且自動應(yīng)用你給定的license

可以在當(dāng)前應(yīng)用運(yùn)行,或者你可以指定一個相對路徑,如果目錄不存在,他會自己創(chuàng)建一個.

執(zhí)行需要提供--pkg-name,在非空的目錄中也能成功執(zhí)行

mkdir -p newApp && cd newApp
cobra init --pkg-name github.com/spf13/newApp

or

cobra init --pkg-name github.com/spf13/newApp path/to/newApp

cobra add

用于添加新的command,舉個例子

  • app serve
  • app config
  • app config create

在項目目錄上執(zhí)行以下命令即可

cobra add serve
cobra add config
cobra add create -p 'configCmd'

command采用駝峰式命名,不然可能會遇到錯誤.For example, cobra add add-user is incorrect, but cobra add addUser is valid.

Once you have run these three commands you would have an app structure similar to the following:

執(zhí)行以上命令后的項目結(jié)構(gòu)

  ? app/
    ? cmd/
        serve.go
        config.go
        create.go
      main.go

配置文件

提供配置文件可以避免每次使用提供一堆信息.

舉個例子 ~/.cobra.yaml :

author: Steve Francia <spf@spf13.com>
license: MIT

你也可以使用其他內(nèi)置license,比如GPLv2, GPLv3, LGPL, AGPL, MIT, 2-Clause BSD or 3-Clause BSD.

也可以不使用證書,把license設(shè)置為none即可,或者你也可以自定義license

author: Steve Francia <spf@spf13.com>
year: 2020
license:
  header: This file is part of CLI application foo.
  text: |
    {{ .copyright }}

    This is my license. There are many like it, but this one is mine.
    My license is my best friend. It is my life. I must master it as I must
    master my life.

上面的copyright部分是用author和year兩個屬性生成的,

上面的例子生成的內(nèi)容是:

Copyright ? 2020 Steve Francia <spf@spf13.com>

This is my license. There are many like it, but this one is mine.
My license is my best friend. It is my life. I must master it as I must
master my life.

header也會在license頭部被使用.

/*
Copyright ? 2020 Steve Francia <spf@spf13.com>
This file is part of CLI application foo.
*/
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末返吻,一起剝皮案震驚了整個濱河市平绩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖遥倦,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隐解,死亡現(xiàn)場離奇詭異饿悬,居然都是意外死亡捐顷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門狸膏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沟饥,“玉大人,你說我怎么就攤上這事湾戳∠涂酰” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵砾脑,是天一觀的道長幼驶。 經(jīng)常有香客問我,道長韧衣,這世上最難降的妖魔是什么盅藻? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮畅铭,結(jié)果婚禮上萧求,老公的妹妹穿的比我還像新娘。我一直安慰自己顶瞒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布元旬。 她就那樣靜靜地躺著榴徐,像睡著了一般守问。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坑资,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天耗帕,我揣著相機(jī)與錄音,去河邊找鬼袱贮。 笑死仿便,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的攒巍。 我是一名探鬼主播嗽仪,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柒莉!你這毒婦竟也來了闻坚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤兢孝,失蹤者是張志新(化名)和其女友劉穎窿凤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跨蟹,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡雳殊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了窗轩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夯秃。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖品姓,靈堂內(nèi)的尸體忽然破棺而出寝并,到底是詐尸還是另有隱情,我是刑警寧澤腹备,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布衬潦,位于F島的核電站,受9級特大地震影響植酥,放射性物質(zhì)發(fā)生泄漏镀岛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一友驮、第九天 我趴在偏房一處隱蔽的房頂上張望漂羊。 院中可真熱鬧,春花似錦卸留、人聲如沸走越。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旨指。三九已至赏酥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谆构,已是汗流浹背裸扶。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留搬素,地道東北人呵晨。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像熬尺,于是被迫代替她去往敵國和親摸屠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345