基于Cobra的golang命令行工具開發(fā)

基于golang語言開發(fā)命令行工具(command line interfaces,CLI)最常用的框架是Cobra稚晚。通過Cobra可以使用簡單的接口實(shí)現(xiàn)一個(gè)強(qiáng)大現(xiàn)代的CLI工具烛缔,許多知名的項(xiàng)目比如Docker、Kubernetes等都用Cobra來開發(fā)自己的命令行工具。下面我們將對Cobra的基本用法做簡要介紹。

  • 概覽
  • 相關(guān)概念
    • Commands(命令)
    • Flags(標(biāo)志)
  • 安裝
  • 開始使用
    • 使用Cobra生成器
    • 使用Cobra庫
    • 使用Flags
    • 位置和自定義參數(shù)
    • 示例
    • Help Command
    • 展示Usage
    • 預(yù)處理和后處理等Hooks函數(shù)
    • 處理 unknown command 的建議
    • 生成命令行文檔
    • 實(shí)現(xiàn)命令自動補(bǔ)全

概覽


Cobra提供簡單的接口來創(chuàng)建強(qiáng)大的現(xiàn)代化CLI接口,比如git與go。Cobra同時(shí)也提供一個(gè)二進(jìn)制工具路狮,用于創(chuàng)建命令行程序。

Cobra提供:

  • 簡單的子命令模式: 如app server蔚约,app fetch
  • flags兼容posix模式(包括長奄妨、短版本)
  • 支持子命令嵌套
  • 支持全局、局部以及繼承falgs
  • 智能提示苹祟,如app srver砸抛,將提示srver子命令不存在,是否為app server
  • 自動生成子命令及其flags
  • 自動生成-h树枫,--help等flags提醒
  • 自動生成命令行docs和man文件
  • 支持命令行別名
  • 靈活定義help和usage信息
  • 可與viper庫結(jié)合使用直焙,方便參數(shù)、配置和環(huán)境變量的管理

相關(guān)概念


Cobra的構(gòu)建基于結(jié)構(gòu)化的commands砂轻,arguments和flags奔誓,即命令、參數(shù)和標(biāo)志搔涝。

Commands代表命令行程序執(zhí)行什么命令厨喂,Args(即arguments)和Flags即這些命令的修飾符。

最好的命令行程序應(yīng)該像讀句子那樣去使用体谒,用戶通過命令行本身就可以自然地知道這段命令執(zhí)行的是什么操作杯聚。

一個(gè)典型的命令行設(shè)計(jì)模式類似APPNAME COMMAND ARG --FLAGAPPNAME VERB NOUN --ADJECTIVE

例如下面的例子抒痒,‘server’是一個(gè)操作幌绍, ‘port’是一個(gè)標(biāo)志:

hugo server --port=1313

下面的另一個(gè)例子告訴我們,它要通過Git來執(zhí)行clone操作拷貝url對應(yīng)的項(xiàng)目到本地的裸倉庫:

git clone URL --bare

Commands(命令)


Command是一個(gè)命令行程序最重要的概念。命令行程序支持的每一個(gè)交互操作都應(yīng)該被包含在一個(gè)command中傀广。一條command可以有多條子commands并且能夠可選地執(zhí)行它們颁独。

在上面的例子中, ‘server’就是一條command伪冰。

點(diǎn)擊查看更多有關(guān)Cobra Command的信息

Flags(標(biāo)志)


一個(gè)Flag用來控制command的行為誓酒。Cobra完全兼容POSIX的flags模式,如同Go自帶的標(biāo)準(zhǔn)flag庫那樣贮聂。一條Cobra生成的command可以將它的flags繼承給它的子commadn靠柑,也可以限定這些flags只能被該command使用。

在上面的例子中吓懈,‘port’即一個(gè)flag歼冰。

更加強(qiáng)大的flag功能可以使用pflag庫,它是一個(gè)標(biāo)準(zhǔn)flag庫的擴(kuò)展耻警。

安裝


使用Cobra十分簡單隔嫡。首先通過go get安裝最新版本的代碼庫及相關(guān)依賴,這條命令同時(shí)也會安裝cobra可執(zhí)行程序:

go get -u github.com/spf13/cobra/cobra

接下來甘穿,在golang代碼中引用Cobra:

import "github.com/spf13/cobra"

開始使用


通常一個(gè)Cobra程序遵循如下所示的組織結(jié)構(gòu)腮恩。當(dāng)然,你也可以自己定義合適的結(jié)構(gòu)温兼。

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

Cobra程序中的main.go文件非常簡單秸滴,它通常只做一件事,就是初始化Cobra妨托。

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

使用Cobra生成器


Cobra提供一個(gè)命令行程序cobra來快速生成你想要的任何命令行程序的框架缸榛。本文不介紹Cobra生成器的使用吝羞,關(guān)于該工具的詳細(xì)信息點(diǎn)擊此處兰伤。

使用Cobra庫


使用Cobra,需要創(chuàng)建一個(gè)main.go文件和一個(gè)rootCmd文件钧排。當(dāng)然敦腔,你也可以選擇別的地方去添加額外的commands。

創(chuàng)建rootCmd

Cobra沒有構(gòu)造函數(shù)恨溜,所以直接簡單地創(chuàng)建一個(gè)command對象就行符衔。

這里假設(shè)下面的代碼位于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.Println(err)
    os.Exit(1)
  }
}

你也可以在init()函數(shù)中定義和處理flags和配置。

例如 cmd/root.go:

package cmd

import (
    "fmt"

    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 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())
    }
}

創(chuàng)建main.go

在cmd/root.go文件中我們已經(jīng)定義了一個(gè)名為rootCmd的command作為根糟袁,為了執(zhí)行該command判族,還需要將其放入main.go文件中執(zhí)行。

在一個(gè)Cobra程序中项戴,main.go文件通常只干一件事形帮,即初始化Cobra并執(zhí)行command。

package main

import (
  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

添加其他command

不同的command通常分別在不同的go文件中定義,這里假設(shè)所有command的實(shí)現(xiàn)都處于cmd/目錄下不同的文件中辩撑。
例如界斜,如果你想創(chuàng)建名為version的command,可以創(chuàng)建cmd/version.go文件合冀,并在文件里這么寫:

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")
  },
}

使用Flags


Flags提供控制command行為的能力各薇。

給command綁定flags

一般情況下,我們需要預(yù)先定義變量來存儲flags的值君躺,這樣便于我們各處使用它們(Cobra也支持不顯式地定義和使用flags峭判,但這種情況我們后面再說)。

如下棕叫,我們定義了Verbose和Source兩個(gè)不同類型的變量來表示和存儲flags值朝抖。

var Verbose bool
var Source string

Cobra有兩種不同的方式給command綁定flags,分別為Persistent Flags和Local Flags谍珊。

Persistent Flags( 持久型flags)

Persistent Flags表示flag不僅綁定在一個(gè)command上治宣,同時(shí)也綁定在了這個(gè)command的子command上。Persistent Flags可以被一個(gè)command下的所有子command使用砌滞。

例如侮邀,我們給根command(即rootCmd)綁定了一個(gè)名為‘verbose’的Persistent Flag,那么這個(gè)flag就成了一個(gè)全局flag贝润,可以被所有command使用(因?yàn)閞ootCommand為根command绊茧,顯然后續(xù)所有增加的command都為子command)。

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

Local Flags(本地flags)

Local Flags表示flag僅能被其綁定的command使用打掘。

如下我們將名為'source'的flag綁定給一個(gè)名為localCmd的command华畏,除了localCmd外,其他command無法接收到這個(gè)flag的值尊蚁,即只有l(wèi)oalCmd能給‘Source’變量通過指定flag來賦值亡笑。

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

command定義的Local Flags只能由綁定了這些flags的command使用,但這里還有一種方便的方式横朋,可以使得一個(gè)command所有的子command定的flags都綁定給他們的父command仑乌,只要在創(chuàng)建command時(shí),指定TraverseChildren為true即可琴锭,這樣父command在真正執(zhí)行前會遍歷其所有的子command來綁定flags晰甚。

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

給flags綁定配置

在使用命令行程序時(shí)不總會顯式地提供flags的值,有時(shí)希望程序自己去環(huán)境變量决帖、配置文件等地方自動尋找配置參數(shù)厕九。這時(shí)我們可以通過viper庫來實(shí)現(xiàn)這一功能,將flags的值綁定給viper地回。viper庫是一個(gè)配置管理庫扁远,它可以方便地從配置文件腺阳、環(huán)境變量和遠(yuǎn)端等多種源來獲取配置。

var author string

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

在上面例子中我們將author這個(gè)flag綁定給了viper穿香,viper一般會按照flag亭引、環(huán)境變量、配置文件和默認(rèn)值的優(yōu)先級去尋找環(huán)境變量皮获,當(dāng)用戶沒有通過--author給出flag值時(shí)焙蚓,viper會降低優(yōu)先級去別處尋找匹配的參數(shù)值。

Required flags

默認(rèn)情況下洒宝,是否指定flags是可選的购公,如果你希望當(dāng)一個(gè)flag沒有設(shè)置時(shí),命令行報(bào)錯雁歌,你可以標(biāo)記它為必須的

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

位置和自定義參數(shù)


驗(yàn)證和限制命令行程序的位置參數(shù)可以通過CommandArgs字段來實(shí)現(xiàn)宏浩。

Cobra內(nèi)置有下列驗(yàn)證函數(shù)給Args字段使用:

  • NoArgs - 不允許有位置參數(shù)
  • ArbitraryArgs - 可以接受任意多個(gè)位置參數(shù)
  • OnlyValidArgs - 只允許指定CommandValidArgs字段里指定的位置參數(shù)
  • MinumumNArgs(int) - 限定最少提供多少個(gè)位置參數(shù)
  • MaximumNArgs(int) - 限定最多能提供多少個(gè)位置參數(shù)
  • ExactArgs(int) - 限定必須提供多少個(gè)對應(yīng)的位置參數(shù)
  • ExcatValidArgs(int) - 限定必須提供多少個(gè)對應(yīng)的位置參數(shù),并且位置參數(shù)必須位于ValidArgs字段
  • RangeArgs(min, max) - 限定位置參數(shù)的個(gè)數(shù)必須處于某一個(gè)區(qū)間內(nèi)

以下為一個(gè)限制位置參數(shù)的例子:

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!")
  },
}

示例


目前為止我們已經(jīng)介紹了很多Cobra的基本用法靠瞎,本節(jié)會給出一個(gè)完整的例子來回顧前面講過的知識比庄。

在下面的例子中,我們定義了3個(gè)commands乏盐。兩個(gè)commands為頂級命令佳窑,一個(gè)command為頂級命令的子命令。在這個(gè)例子中父能,由于rootCmd沒有為Run字段提供方法神凑,所以單獨(dú)的root是不能運(yùn)行的,必須要有子commands何吝。

注意這里我們只為一個(gè)名為echoTimes的command 設(shè)置了flag溉委。更多flags的用法參考https://github.com/spf13/pflag

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()
}

如果想要參考更完整和大型的例子爱榕,請點(diǎn)擊Hugo瓣喊。

Help Command


當(dāng)你的程序有子命令時(shí),Cobra 會自動給你程序添加help命令呆细。當(dāng)你運(yùn)行app help型宝,會調(diào)用help命令。另外絮爷,help同樣支持其它輸入命令。例如梨树,你有一個(gè)沒有任何其它配置的命令叫create坑夯,當(dāng)你調(diào)用app help create Corbra 將會起作用。

例子

下面的輸出是Cobra自動生成的抡四,除了command和flag的定義柜蜈,我們沒有對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 就跟其它命令一樣,并沒有特殊的邏輯或行為淑履。事實(shí)上隶垮,你也可以提供你自己定義的help。

自定義Help

你可以使用下面的函數(shù)來定義自己的help:

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

后兩個(gè)函數(shù)定義的help會被command的子命令繼承秘噪。

展示Usage


當(dāng)用戶錯誤的使用命令行程序時(shí)(如指定非法的flag和command)狸吞,Cobra將會自動顯示命令行程序的用法說明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.

自定義用法說明

你能提供你自己的usage函數(shù)或模板給 Cobra 使用指煎。
類似于自定義help蹋偏,usage的方法和模板都會覆蓋默認(rèn)的公共說明。

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

版本Flag


當(dāng)頂級Command的Version字段被定義后至壤,Cobra會自動為頂級command添加一個(gè)--versionflag威始。當(dāng)命令行程序指定該flag時(shí),Cobra會調(diào)用內(nèi)置的版本函數(shù)打印命令行程序的相關(guān)版本信息像街。當(dāng)然黎棠,你也可以使用cmd.SetVersionTemplate(s string)函數(shù)來自定義版本信息的展示內(nèi)容。

預(yù)處理和后處理等Hooks函數(shù)


Cobra提供了多個(gè)鉤子(hooks)函數(shù)的接口镰绎,你可以很容易地去決定在command執(zhí)行Run中的實(shí)際函數(shù)之前或之后葫掉,需要執(zhí)行哪些方法。

PersistentPreRunPreRun函數(shù)會在Run之前執(zhí)行跟狱。

PersistentPostRunPostRun函數(shù)將會在Run之后執(zhí)行俭厚。

Persistent*Run這種模式的函數(shù)會被子command繼承。

鉤子函數(shù)的執(zhí)行順序如下:

  • PersistentPreRun

  • PreRun

  • Run

  • PostRun

  • PersistentPostRun

如下的例子使用了上面提到的所有鉤子函數(shù)驶臊。需要注意的是挪挤,當(dāng)子command執(zhí)行時(shí),它會執(zhí)行根command的PersistentPreRun函數(shù)而不會執(zhí)行根command的PersistentPostRun函數(shù)(因?yàn)樽觕ommand自己定義了該函數(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()
}

輸出:

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]

處理 unknown command 的建議


Cobra在unknown command錯誤發(fā)生時(shí)扛门,會自動打印建議。這就讓Cobra的處理錯誤行為的方式類似git命令那樣纵寝。例如:

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

Did you mean this?
        server

Run 'hugo --help' for usage.

建議會基于注冊的子命令自動生成论寨。使用了Levenshtein distance的實(shí)現(xiàn)。每一個(gè)模糊匹配的命令間隔為2個(gè)字符爽茴。

如果你希望在你的命令里葬凳,禁用建議或減小字符串的距離,使用:

command.DisableSuggestions = true

command.SuggestionsMinimumDistance = 1

你也可以通過SuggestFor來給命令提供明確的名詞建議室奏。這個(gè)特性允許當(dāng)字符串不相近火焰,但是意思與你的命令相近時(shí),提供指定的命令建議胧沫,比如:

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

Did you mean this?
        delete

Run 'kubectl help' for usage.

生成命令行文檔


Cobra可以基于command昌简、flags等來自動生成文檔占业,支持下面幾種格式:

實(shí)現(xiàn)命令自動補(bǔ)全


Cobra還提供了自動生成bash或zsh自動補(bǔ)全腳本的功能,這部分參見

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纯赎,一起剝皮案震驚了整個(gè)濱河市谦疾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌犬金,老刑警劉巖念恍,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異佑附,居然都是意外死亡樊诺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門音同,熙熙樓的掌柜王于貴愁眉苦臉地迎上來词爬,“玉大人,你說我怎么就攤上這事权均《倥颍” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵叽赊,是天一觀的道長恋沃。 經(jīng)常有香客問我,道長必指,這世上最難降的妖魔是什么囊咏? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮塔橡,結(jié)果婚禮上梅割,老公的妹妹穿的比我還像新娘。我一直安慰自己葛家,他們只是感情好户辞,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著癞谒,像睡著了一般底燎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弹砚,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天双仍,我揣著相機(jī)與錄音,去河邊找鬼迅栅。 笑死殊校,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的读存。 我是一名探鬼主播为流,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼让簿!你這毒婦竟也來了敬察?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤尔当,失蹤者是張志新(化名)和其女友劉穎莲祸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椭迎,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锐帜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畜号。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缴阎。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖简软,靈堂內(nèi)的尸體忽然破棺而出蛮拔,到底是詐尸還是另有隱情,我是刑警寧澤痹升,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布建炫,位于F島的核電站,受9級特大地震影響疼蛾,放射性物質(zhì)發(fā)生泄漏肛跌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一察郁、第九天 我趴在偏房一處隱蔽的房頂上張望衍慎。 院中可真熱鬧,春花似錦绳锅、人聲如沸西饵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眷柔。三九已至,卻和暖如春原朝,著一層夾襖步出監(jiān)牢的瞬間驯嘱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工喳坠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鞠评,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓壕鹉,卻偏偏與公主長得像剃幌,于是被迫代替她去往敵國和親聋涨。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350