分类 技术 下的文章

如果你的 LVM 不再需要使用某个设备,你可以使用 vgreduce 命令从卷组中删除物理卷。

vgreduce 命令可以通过删除物理卷来缩小卷组的容量。但要确保该物理卷没有被任何逻辑卷使用,请使用 pvdisplay 命令查看。如果物理卷仍在使用,你必须使用 pvmove 命令将数据转移到另一个物理卷。

数据转移后,它就可以从卷组中删除。

最后使用 pvremove 命令删除空物理卷上的 LVM 标签和 LVM 元数据。

将扩展块移动到现有物理卷上

使用 pvs 命令检查是否使用了所需的物理卷(我们计划删除 LVM 中的 /dev/sdc 磁盘)。

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G 14.00G 61.00G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 12.15G 5.00G

如果使用了,请检查卷组中的其他物理卷是否有足够的空闲 扩展块 extent

如果有的话,你可以在需要删除的设备上运行 pvmove 命令。扩展块将被分配到其他设备上。

# pvmove /dev/sdc

/dev/sdc: Moved: 2.0%
…
/dev/sdc: Moved: 79.2%
…
/dev/sdc: Moved: 100.0%

pvmove 命令完成后。再次使用 pvs 命令检查物理卷是否有空闲。

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G  1.85G 73.15G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 17.15G 0

如果它是空闲的,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdc

# vgreduce myvg /dev/sdc
Removed "/dev/sdc" from volume group "vg01"

最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。

# pvremove /dev/sdc
Labels on physical volume "/dev/sdc" successfully wiped.

移动扩展块到新磁盘

如果你在卷组中的其他物理卷上没有足够的可用扩展。使用以下步骤添加新的物理卷。

向存储组申请新的 LUN。分配完毕后,运行以下命令来在 Linux 中发现新添加的 LUN 或磁盘

# ls /sys/class/scsi_host
host0
# echo "- - -" > /sys/class/scsi_host/host0/scan
# fdisk -l

操作系统中检测到磁盘后,使用 pvcreate 命令创建物理卷。

# pvcreate /dev/sdd
Physical volume "/dev/sdd" successfully created

使用以下命令将新的物理卷 /dev/sdd 添加到现有卷组 vg01 中。

# vgextend vg01 /dev/sdd
Volume group "vg01" successfully extended

现在,使用 pvs 命令查看你添加的新磁盘 /dev/sdd

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G 14.00G 61.00G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 12.15G 5.00G
/dev/sdd myvg lvm2 a- 60.00G 60.00G 0

使用 pvmove 命令将数据从 /dev/sdc 移动到 /dev/sdd

# pvmove /dev/sdc /dev/sdd

/dev/sdc: Moved: 10.0%
…
/dev/sdc: Moved: 79.7%
…
/dev/sdc: Moved: 100.0%

数据移动到新磁盘后。再次使用 pvs 命令检查物理卷是否空闲。

# pvs -o+pv_used

PV VG Fmt Attr PSize PFree Used
/dev/sda myvg lvm2 a- 75.00G 14.00G 61.00G
/dev/sdb myvg lvm2 a- 50.00G 45.00G 5.00G
/dev/sdc myvg lvm2 a- 17.15G 17.15G 0
/dev/sdd myvg lvm2 a- 60.00G 47.85G 12.15G

如果空闲,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdc

# vgreduce myvg /dev/sdc
Removed "/dev/sdc" from volume group "vg01"

最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。

# pvremove /dev/sdc
Labels on physical volume "/dev/sdc" successfully wiped.

via: https://www.2daygeek.com/linux-remove-delete-physical-volume-pv-from-volume-group-vg-in-lvm/

作者:Magesh Maruthamuthu 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本文的 第一部分 的结尾,我承诺要写关于接口的内容。我不想在这里写有关接口或完整或简短的讲义。相反,我将展示一个简单的示例,来说明如何定义和使用接口,以及如何利用无处不在的 io.Writer 接口。还有一些关于 反射 reflection 半主机 semihosting 的内容。

STM32F030F4P6]

接口是 Go 语言的重要组成部分。如果你想了解更多有关它们的信息,我建议你阅读《高效的 Go 编程》 和 Russ Cox 的文章

并发 Blinky – 回顾

当你阅读前面示例的代码时,你可能会注意到一中打开或关闭 LED 的反直觉方式。 Set 方法用于关闭 LED,Clear 方法用于打开 LED。这是由于在 漏极开路配置 open-drain configuration 下驱动了 LED。我们可以做些什么来减少代码的混乱?让我们用 OnOff 方法来定义 LED 类型:

type LED struct {
    pin gpio.Pin
}

func (led LED) On() {
    led.pin.Clear()
}

func (led LED) Off() {
    led.pin.Set()
}

现在我们可以简单地调用 led.On()led.Off(),这不会再引起任何疑惑了。

在前面的所有示例中,我都尝试使用相同的 漏极开路配置 open-drain configuration 来避免代码复杂化。但是在最后一个示例中,对于我来说,将第三个 LED 连接到 GND 和 PA3 引脚之间并将 PA3 配置为 推挽模式 push-pull mode 会更容易。下一个示例将使用以此方式连接的 LED。

但是我们的新 LED 类型不支持推挽配置,实际上,我们应该将其称为 OpenDrainLED,并定义另一个类型 PushPullLED

type PushPullLED struct {
    pin gpio.Pin
}

func (led PushPullLED) On() {
    led.pin.Set()
}

func (led PushPullLED) Off() {
    led.pin.Clear()
}

请注意,这两种类型都具有相同的方法,它们的工作方式也相同。如果在 LED 上运行的代码可以同时使用这两种类型,而不必注意当前使用的是哪种类型,那就太好了。 接口类型可以提供帮助:

package main

import (
    "delay"

    "stm32/hal/gpio"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

type LED interface {
    On()
    Off()
}

type PushPullLED struct{ pin gpio.Pin }

func (led PushPullLED) On()  {
    led.pin.Set()
}

func (led PushPullLED) Off() {
    led.pin.Clear()
}

func MakePushPullLED(pin gpio.Pin) PushPullLED {
    pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.PushPull})
    return PushPullLED{pin}
}

type OpenDrainLED struct{ pin gpio.Pin }

func (led OpenDrainLED) On()  {
    led.pin.Clear()
}

func (led OpenDrainLED) Off() {
    led.pin.Set()
}

func MakeOpenDrainLED(pin gpio.Pin) OpenDrainLED {
    pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain})
    return OpenDrainLED{pin}
}

var led1, led2 LED

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    led1 = MakeOpenDrainLED(gpio.A.Pin(4))
    led2 = MakePushPullLED(gpio.A.Pin(3))
}

func blinky(led LED, period int) {
    for {
        led.On()
        delay.Millisec(100)
        led.Off()
        delay.Millisec(period - 100)
    }
}

func main() {
    go blinky(led1, 500)
    blinky(led2, 1000)
}

我们定义了 LED 接口,它有两个方法: OnOffPushPullLEDOpenDrainLED 类型代表两种驱动 LED 的方式。我们还定义了两个用作构造函数的 Make*LED 函数。这两种类型都实现了 LED 接口,因此可以将这些类型的值赋给 LED 类型的变量:

led1 = MakeOpenDrainLED(gpio.A.Pin(4))
led2 = MakePushPullLED(gpio.A.Pin(3))

在这种情况下, 可赋值性 assignability 在编译时检查。赋值后,led1 变量包含一个 OpenDrainLED{gpio.A.Pin(4)},以及一个指向 OpenDrainLED 类型的方法集的指针。 led1.On() 调用大致对应于以下 C 代码:

led1.methods->On(led1.value)

如你所见,如果仅考虑函数调用的开销,这是相当廉价的抽象。

但是,对接口的任何赋值都会导致包含有关已赋值类型的大量信息。对于由许多其他类型组成的复杂类型,可能会有很多信息:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10356     196     212   10764    2a0c cortexm0.elf

如果我们不使用 反射,可以通过避免包含类型和结构字段的名称来节省一些字节:

$ egc -nf -nt
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10312     196     212   10720    29e0 cortexm0.elf

生成的二进制文件仍然包含一些有关类型的必要信息和关于所有导出方法(带有名称)的完整信息。在运行时,主要是当你将存储在接口变量中的一个值赋值给任何其他变量时,需要此信息来检查可赋值性。

我们还可以通过重新编译所导入的包来删除它们的类型和字段名称:

$ cd $HOME/emgo
$ ./clean.sh
$ cd $HOME/firstemgo
$ egc -nf -nt
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  10272     196     212   10680    29b8 cortexm0.elf

让我们加载这个程序,看看它是否按预期工作。这一次我们将使用 st-flash 命令:

$ arm-none-eabi-objcopy -O binary cortexm0.elf cortexm0.bin
$ st-flash write cortexm0.bin 0x8000000
st-flash 1.4.0-33-gd76e3c7
2018-04-10T22:04:34 INFO usb.c: -- exit_dfu_mode
2018-04-10T22:04:34 INFO common.c: Loading device parameters....
2018-04-10T22:04:34 INFO common.c: Device connected is: F0 small device, id 0x10006444
2018-04-10T22:04:34 INFO common.c: SRAM size: 0x1000 bytes (4 KiB), Flash: 0x4000 bytes (16 KiB) in pages of 1024 bytes
2018-04-10T22:04:34 INFO common.c: Attempting to write 10468 (0x28e4) bytes to stm32 address: 134217728 (0x8000000)
Flash page at addr: 0x08002800 erased
2018-04-10T22:04:34 INFO common.c: Finished erasing 11 pages of 1024 (0x400) bytes
2018-04-10T22:04:34 INFO common.c: Starting Flash write for VL/F0/F3/F1_XL core id
2018-04-10T22:04:34 INFO flash_loader.c: Successfully loaded flash loader in sram
 11/11 pages written
2018-04-10T22:04:35 INFO common.c: Starting verification of write complete
2018-04-10T22:04:35 INFO common.c: Flash written and verified! jolly good!

我没有将 NRST 信号连接到编程器,因此无法使用 -reset 选项,必须按下复位按钮才能运行程序。

Interfaces

看来,st-flash 与此板配合使用有点不可靠(通常需要复位 ST-LINK 加密狗)。此外,当前版本不会通过 SWD 发出复位命令(仅使用 NRST 信号)。软件复位是不现实的,但是它通常是有效的,缺少它会将会带来不便。对于 板卡程序员 board-programmer 来说 OpenOCD 工作得更好。

UART

UART( 通用异步收发传输器 Universal Aynchronous Receiver-Transmitter )仍然是当今微控制器最重要的外设之一。它的优点是以下属性的独特组合:

  • 相对较高的速度,
  • 仅两条信号线(在 半双工 half-duplex 通信的情况下甚至一条),
  • 角色对称,
  • 关于新数据的 同步带内信令 synchronous in-band signaling (起始位),
  • 在传输 words 内的精确计时。

这使得最初用于传输由 7-9 位的字组成的异步消息的 UART,也被用于有效地实现各种其他物理协议,例如被 WS28xx LEDs1-wire 设备使用的协议。

但是,我们将以其通常的角色使用 UART:从程序中打印文本消息。

package main

import (
    "io"
    "rtos"

    "stm32/hal/dma"
    "stm32/hal/gpio"
    "stm32/hal/irq"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
    "stm32/hal/usart"
)

var tts *usart.Driver

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(true)
    tx := gpio.A.Pin(9)

    tx.Setup(&gpio.Config{Mode: gpio.Alt})
    tx.SetAltFunc(gpio.USART1_AF1)
    d := dma.DMA1
    d.EnableClock(true)
    tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
    tts.Periph().EnableClock(true)
    tts.Periph().SetBaudRate(115200)
    tts.Periph().Enable()
    tts.EnableTx()

    rtos.IRQ(irq.USART1).Enable()
    rtos.IRQ(irq.DMA1_Channel2_3).Enable()
}

func main() {
    io.WriteString(tts, "Hello, World!
")
}

func ttsISR() {
    tts.ISR()
}

func ttsDMAISR() {
    tts.TxDMAISR()
}

//c:__attribute__((section(".ISRs")))
var ISRs = [...]func(){
    irq.USART1:          ttsISR,
    irq.DMA1_Channel2_3: ttsDMAISR,
}

你会发现此代码可能有些复杂,但目前 STM32 HAL 中没有更简单的 UART 驱动程序(在某些情况下,简单的轮询驱动程序可能会很有用)。 usart.Driver 是使用 DMA 和中断来减轻 CPU 负担的高效驱动程序。

STM32 USART 外设提供传统的 UART 及其同步版本。要将其用作输出,我们必须将其 Tx 信号连接到正确的 GPIO 引脚:

tx.Setup(&gpio.Config{Mode: gpio.Alt})
tx.SetAltFunc(gpio.USART1_AF1)

在 Tx-only 模式下配置 usart.Driver (rxdma 和 rxbuf 设置为 nil):

tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)

我们使用它的 WriteString 方法来打印这句名言。让我们清理所有内容并编译该程序:

$ cd $HOME/emgo
$ ./clean.sh
$ cd $HOME/firstemgo
$ egc
$ arm-none-eabi-size cortexm0.elf
  text       data        bss        dec        hex    filename
  12728        236        176      13140       3354    cortexm0.elf

要查看某些内容,你需要在 PC 中使用 UART 外设。

请勿使用 RS232 端口或 USB 转 RS232 转换器!

STM32 系列使用 3.3V 逻辑,但是 RS232 可以产生 -15 V ~ +15 V 的电压,这可能会损坏你的 MCU。你需要使用 3.3V 逻辑的 USB 转 UART 转换器。流行的转换器基于 FT232 或 CP2102 芯片。

UART

你还需要一些终端仿真程序(我更喜欢 picocom)。刷新新图像,运行终端仿真器,然后按几次复位按钮:

$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 0
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x080016f4 msp: 0x20000a20
adapter speed: 4000 kHz
** Programming Started **
auto erase enabled
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
wrote 13312 bytes from file cortexm0.elf in 1.020185s (12.743 KiB/s)
** Programming Finished **
adapter speed: 950 kHz
$
$ picocom -b 115200 /dev/ttyUSB0
picocom v3.1

port is        : /dev/ttyUSB0
flowcontrol    : none
baudrate is    : 115200
parity is      : none
databits are   : 8
stopbits are   : 1
escape is      : C-a
local echo is  : no
noinit is      : no
noreset is     : no
hangup is      : no
nolock is      : no
send_cmd is    : sz -vv
receive_cmd is : rz -vv -E
imap is        :
omap is        :
emap is        : crcrlf,delbs,
logfile is     : none
initstring     : none
exit_after is  : not set
exit is        : no

Type [C-a] [C-h] to see available commands
Terminal ready
Hello, World!
Hello, World!
Hello, World!

每次按下复位按钮都会产生新的 “Hello,World!”行。一切都在按预期进行。

要查看此 MCU 的 双向 bi-directional UART 代码,请查看 此示例

io.Writer 接口

io.Writer 接口可能是 Go 中第二种最常用的接口类型,仅次于 error 接口。其定义如下所示:

type Writer interface {
    Write(p []byte) (n int, err error)
}

usart.Driver 实现了 io.Writer,因此我们可以替换:

tts.WriteString("Hello, World!
")

io.WriteString(tts, "Hello, World!
")

此外,你需要将 io 包添加到 import 部分。

io.WriteString 函数的声明如下所示:

func WriteString(w Writer, s string) (n int, err error)

如你所见,io.WriteString 允许使用实现了 io.Writer 接口的任何类型来编写字符串。在内部,它检查基础类型是否具有 WriteString 方法,并使用该方法代替 Write(如果可用)。

让我们编译修改后的程序:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15456     320     248   16024    3e98 cortexm0.elf

如你所见,io.WriteString 导致二进制文件的大小显着增加:15776-12964 = 2812 字节。 Flash 上没有太多空间了。是什么引起了这么大规模的增长?

使用这个命令:

arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf

我们可以打印两种情况下按其大小排序的所有符号。通过过滤和分析获得的数据(awkdiff),我们可以找到大约 80 个新符号。最大的十个如下所示:

> 00000062 T stm32$hal$usart$Driver$DisableRx
> 00000072 T stm32$hal$usart$Driver$RxDMAISR
> 00000076 T internal$Type$Implements
> 00000080 T stm32$hal$usart$Driver$EnableRx
> 00000084 t errors$New
> 00000096 R $8$stm32$hal$usart$Driver$$
> 00000100 T stm32$hal$usart$Error$Error
> 00000360 T io$WriteString
> 00000660 T stm32$hal$usart$Driver$Read

因此,即使我们不使用 usart.Driver.Read 方法,但它被编译进来了,与 DisableRxRxDMAISREnableRx 以及上面未提及的其他方法一样。不幸的是,如果你为接口赋值了一些内容,就需要它的完整方法集(包含所有依赖项)。对于使用大多数方法的大型程序来说,这不是问题。但是对于我们这种极简的情况而言,这是一个巨大的负担。

我们已经接近 MCU 的极限,但让我们尝试打印一些数字(你需要在 import 部分中用 strconv 替换 io 包):

func main() {
    a := 12
    b := -123

    tts.WriteString("a = ")
    strconv.WriteInt(tts, a, 10, 0, 0)
    tts.WriteString("
")
    tts.WriteString("b = ")
    strconv.WriteInt(tts, b, 10, 0, 0)
    tts.WriteString("
")

    tts.WriteString("hex(a) = ")
    strconv.WriteInt(tts, a, 16, 0, 0)
    tts.WriteString("
")
    tts.WriteString("hex(b) = ")
    strconv.WriteInt(tts, b, 16, 0, 0)
    tts.WriteString("
")
}

与使用 io.WriteString 函数的情况一样,strconv.WriteInt 的第一个参数的类型为 io.Writer

$ egc
/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/firstemgo/cortexm0.elf section `.rodata' will not fit in region `Flash'
/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 692 bytes
exit status 1

这一次我们的空间超出的不多。让我们试着精简一下有关类型的信息:

$ cd $HOME/emgo
$ ./clean.sh
$ cd $HOME/firstemgo
$ egc -nf -nt
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15876     316     320   16512    4080 cortexm0.elf

很接近,但很合适。让我们加载并运行此代码:

a = 12
b = -123
hex(a) = c
hex(b) = -7b

Emgo 中的 strconv 包与 Go 中的原型有很大的不同。它旨在直接用于写入格式化的数字,并且在许多情况下可以替换沉重的 fmt 包。 这就是为什么函数名称以 Write 而不是 Format 开头,并具有额外的两个参数的原因。 以下是其用法示例:

func main() {
    b := -123
    strconv.WriteInt(tts, b, 10, 0, 0)
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, 6, ' ')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, 6, '0')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, 6, '.')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, -6, ' ')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, -6, '0')
    tts.WriteString("
")
    strconv.WriteInt(tts, b, 10, -6, '.')
    tts.WriteString("
")
}

下面是它的输出:

-123
  -123
-00123
..-123
-123
-123
-123..

Unix 流 和 莫尔斯电码 Morse code

由于大多数写入的函数都使用 io.Writer 而不是具体类型(例如 C 中的 FILE ),因此我们获得了类似于 Unix stream 的功能。在 Unix 中,我们可以轻松地组合简单的命令来执行更大的任务。例如,我们可以通过以下方式将文本写入文件:

echo "Hello, World!" > file.txt

> 操作符将前面命令的输出流写入文件。还有 | 操作符,用于连接相邻命令的输出流和输入流。

多亏了流,我们可以轻松地转换/过滤任何命令的输出。例如,要将所有字母转换为大写,我们可以通过 tr 命令过滤 echo 的输出:

echo "Hello, World!" | tr a-z A-Z > file.txt

为了显示 io.Writer 和 Unix 流之间的类比,让我们编写以下代码:

io.WriteString(tts, "Hello, World!
")

采用以下伪 unix 形式:

io.WriteString "Hello, World!" | usart.Driver usart.USART1

下一个示例将显示如何执行此操作:

io.WriteString "Hello, World!" | MorseWriter | usart.Driver usart.USART1

让我们来创建一个简单的编码器,它使用莫尔斯电码对写入的文本进行编码:

type MorseWriter struct {
    W io.Writer
}

func (w *MorseWriter) Write(s []byte) (int, error) {
    var buf [8]byte
    for n, c := range s {
        switch {
        case c == '\n':
            c = ' ' // Replace new lines with spaces.
        case 'a' <= c && c <= 'z':
            c -= 'a' - 'A' // Convert to upper case.
        }
        if c < ' ' || 'Z' < c {
            continue // c is outside ASCII [' ', 'Z']
        }
        var symbol morseSymbol
        if c == ' ' {
            symbol.length = 1
            buf[0] = ' '
        } else {
            symbol = morseSymbols[c-'!']
            for i := uint(0); i < uint(symbol.length); i++ {
                if (symbol.code>>i)&1 != 0 {
                    buf[i] = '-'
                } else {
                    buf[i] = '.'
                }
            }
        }
        buf[symbol.length] = ' '
        if _, err := w.W.Write(buf[:symbol.length+1]); err != nil {
            return n, err
        }
    }
    return len(s), nil
}

type morseSymbol struct {
    code, length byte
}

//emgo:const
var morseSymbols = [...]morseSymbol{
    {1<<0 | 1<<1 | 1<<2, 4}, // ! ---.
    {1<<1 | 1<<4, 6},        // " .-..-.
    {},                      // #
    {1<<3 | 1<<6, 7},        // $ ...-..-

    // Some code omitted...

    {1<<0 | 1<<3, 4},        // X -..-
    {1<<0 | 1<<2 | 1<<3, 4}, // Y -.--
    {1<<0 | 1<<1, 4},        // Z --..
}

你可以在 这里 找到完整的 morseSymbols 数组。 //emgo:const 指令确保 morseSymbols 数组不会被复制到 RAM 中。

现在我们可以通过两种方式打印句子:

func main() {
    s := "Hello, World!
"
    mw := &MorseWriter{tts}

    io.WriteString(tts, s)
    io.WriteString(mw, s)
}

我们使用指向 MorseWriter &MorseWriter{tts} 的指针而不是简单的 MorseWriter{tts} 值,因为 MorseWriter 太大,不适合接口变量。

与 Go 不同,Emgo 不会为存储在接口变量中的值动态分配内存。接口类型的大小受限制,相当于三个指针(适合 slice )或两个 float64(适合 complex128)的大小,以较大者为准。它可以直接存储所有基本类型和小型 “结构体/数组” 的值,但是对于较大的值,你必须使用指针。

让我们编译此代码并查看其输出:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  15152     324     248   15724    3d6c cortexm0.elf
Hello, World!
.... . .-.. .-.. --- --..--   .-- --- .-. .-.. -.. ---.

终极闪烁

Blinky 是等效于 “Hello,World!” 程序的硬件。一旦有了摩尔斯编码器,我们就可以轻松地将两者结合起来以获得终极闪烁程序:

package main

import (
    "delay"
    "io"

    "stm32/hal/gpio"
    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

var led gpio.Pin

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    gpio.A.EnableClock(false)
    led = gpio.A.Pin(4)

    cfg := gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain, Speed: gpio.Low}
    led.Setup(&cfg)
}

type Telegraph struct {
    Pin   gpio.Pin
    Dotms int // Dot length [ms]
}

func (t Telegraph) Write(s []byte) (int, error) {
    for _, c := range s {
        switch c {
        case '.':
            t.Pin.Clear()
            delay.Millisec(t.Dotms)
            t.Pin.Set()
            delay.Millisec(t.Dotms)
        case '-':
            t.Pin.Clear()
            delay.Millisec(3 * t.Dotms)
            t.Pin.Set()
            delay.Millisec(t.Dotms)
        case ' ':
            delay.Millisec(3 * t.Dotms)
        }
    }
    return len(s), nil
}

func main() {
    telegraph := &MorseWriter{Telegraph{led, 100}}
    for {
        io.WriteString(telegraph, "Hello, World! ")
    }
}

// Some code omitted...

在上面的示例中,我省略了 MorseWriter 类型的定义,因为它已在前面展示过。完整版可通过 这里 获取。让我们编译它并运行:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  11772     244     244   12260    2fe4 cortexm0.elf

Ultimate Blinky

反射

是的,Emgo 支持 反射reflect 包尚未完成,但是已完成的部分足以实现 fmt.Print 函数族了。来看看我们可以在小型 MCU 上做什么。

为了减少内存使用,我们将使用 半主机 semihosting 作为标准输出。为了方便起见,我们还编写了简单的 println 函数,它在某种程度上类似于 fmt.Println

package main

import (
    "debug/semihosting"
    "reflect"
    "strconv"

    "stm32/hal/system"
    "stm32/hal/system/timer/systick"
)

var stdout semihosting.File

func init() {
    system.SetupPLL(8, 1, 48/8)
    systick.Setup(2e6)

    var err error
    stdout, err = semihosting.OpenFile(":tt", semihosting.W)
    for err != nil {
    }
}

type stringer interface {
    String() string
}

func println(args ...interface{}) {
    for i, a := range args {
        if i > 0 {
            stdout.WriteString(" ")
        }
        switch v := a.(type) {
        case string:
            stdout.WriteString(v)
        case int:
            strconv.WriteInt(stdout, v, 10, 0, 0)
        case bool:
            strconv.WriteBool(stdout, v, 't', 0, 0)
        case stringer:
            stdout.WriteString(v.String())
        default:
            stdout.WriteString("%unknown")
        }
    }
    stdout.WriteString("
")
}

type S struct {
    A int
    B bool
}

func main() {
    p := &S{-123, true}

    v := reflect.ValueOf(p)

    println("kind(p) =", v.Kind())
    println("kind(*p) =", v.Elem().Kind())
    println("type(*p) =", v.Elem().Type())

    v = v.Elem()

    println("*p = {")
    for i := 0; i < v.NumField(); i++ {
        ft := v.Type().Field(i)
        fv := v.Field(i)
        println("  ", ft.Name(), ":", fv.Interface())
    }
    println("}")
}

semihosting.OpenFile 函数允许在主机端打开/创建文件。特殊路径 :tt 对应于主机的标准输出。

println 函数接受任意数量的参数,每个参数的类型都是任意的:

func println(args ...interface{})

可能是因为任何类型都实现了空接口 interface{}println 使用 类型开关 打印字符串,整数和布尔值:

switch v := a.(type) {
case string:
    stdout.WriteString(v)
case int:
    strconv.WriteInt(stdout, v, 10, 0, 0)
case bool:
    strconv.WriteBool(stdout, v, 't', 0, 0)
case stringer:
    stdout.WriteString(v.String())
default:
    stdout.WriteString("%unknown")
}

此外,它还支持任何实现了 stringer 接口的类型,即任何具有 String() 方法的类型。在任何 case 子句中,v 变量具有正确的类型,与 case 关键字后列出的类型相同。

reflect.ValueOf(p) 函数通过允许以编程的方式分析其类型和内容的形式返回 p。如你所见,我们甚至可以使用 v.Elem() 取消引用指针,并打印所有结构体及其名称。

让我们尝试编译这段代码。现在让我们看看如果编译时没有类型和字段名,会有什么结果:

$ egc -nt -nf
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  16028     216     312   16556    40ac cortexm0.elf

闪存上只剩下 140 个可用字节。让我们使用启用了半主机的 OpenOCD 加载它:

$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; arm semihosting enable; reset run'
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
debug_level: 0
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
adapter speed: 950 kHz
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x08002338 msp: 0x20000a20
adapter speed: 4000 kHz
** Programming Started **
auto erase enabled
target halted due to breakpoint, current mode: Thread
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
wrote 16384 bytes from file cortexm0.elf in 0.700133s (22.853 KiB/s)
** Programming Finished **
semihosting is enabled
adapter speed: 950 kHz
kind(p) = ptr
kind(*p) = struct
type(*p) =
*p = {
   X. : -123
   X. : true
}

如果你实际运行此代码,则会注意到半主机运行缓慢,尤其是在逐字节写入时(缓冲很有用)。

如你所见,*p 没有类型名称,并且所有结构字段都具有相同的 X. 名称。让我们再次编译该程序,这次不带 -nt -nf 选项:

$ egc
$ arm-none-eabi-size cortexm0.elf
   text    data     bss     dec     hex filename
  16052     216     312   16580    40c4 cortexm0.elf

现在已经包括了类型和字段名称,但仅在 main.go 文件中 main 包中定义了它们。该程序的输出如下所示:

kind(p) = ptr
kind(*p) = struct
type(*p) = S
*p = {
   A : -123
   B : true
}

反射是任何易于使用的序列化库的关键部分,而像 JSON 这样的序列化 算法 物联网 IoT 时代也越来越重要。

这些就是我完成的本文的第二部分。我认为有机会进行第三部分,更具娱乐性的部分,在那里我们将各种有趣的设备连接到这块板上。如果这块板装不下,我们就换一块大一点的。


via: https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html

作者:Michał Derkacz 译者:gxlct008 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

开始在 Linux 系统上使用开源的 SQL 数据库吧。

MariaDBMySQL 都是使用 SQL 的开源数据库,并且共享相同的初始代码库。MariaDB 是 MySQL 的替代品,你可以使用相同的命令(mysql)与 MySQL 和 MariaDB 数据库进行交互。因此,本文同时适用于 MariaDB 和 MySQL。

安装 MariaDB

你可以使用你的 Linux 发行版的包管理器安装 MariaDB。在大多数发行版上,MariaDB 分为服务器包和客户端包。服务器包提供了数据库“引擎”,即 MariaDB 在后台运行(通常在物理服务器上)的部分,它监听数据输入或数据输出请求。客户端包提供了 mysql 命令,你可以用它来与服务器通信。

在 RHEL、Fedora、CentOS 或类似的发行版上:

$ sudo dnf install mariadb mariadb-server

在 Debian、Ubuntu、Elementary 或类似的发行版上:

$ sudo apt install mariadb-client mariadb-server

其他操作系统可能会以不同的打包系统封装 MariaDB,所以你可能需要搜索你的软件仓库来了解你的发行版的维护者是如何提供它的。

启动 MariaDB

因为 MariaDB 被设计为部分作为数据库服务器,它可以在一台计算机上运行,并从另一台计算机上进行管理。只要你能访问运行它的计算机,你就可以使用 mysql 命令来管理数据库。在写这篇文章时,我在本地计算机上运行了 MariaDB,但你同样可与远程系统上托管的 MariaDB 数据库进行交互。

在启动 MariaDB 之前,你必须创建一个初始数据库。在初始化其文件结构时,你应该定义你希望 MariaDB 使用的用户。默认情况下,MariaDB 使用当前用户,但你可能希望它使用一个专用的用户帐户。你的包管理器可能为你配置了一个系统用户和组。使用 grep 查找是否有一个 mysql 组:

$ grep mysql /etc/group
mysql:x:27:

你也可以在 /etc/passwd 中寻找这个专门的用户,但通常情况下,有组就会有用户。如果没有专门的 mysql 用户和组,可以在 /etc/group 中寻找一个明显的替代品(比如 mariadb)。如果没有,请阅读你的发行版文档来了解 MariaDB 是如何运行的。

假设你的安装使用 mysql,初始化数据库环境:

$ sudo mysql_install_db --user=mysql
Installing MariaDB/MySQL system tables in '/var/lib/mysql'...
OK
[...]

这一步的结果显示了接下来你必须执行的配置 MariaDB 的任务:

PLEASE REMEMBER TO SET A PASSWORD FOR THE MariaDB root USER !
To do so, start the server, then issue the following commands:

'/usr/bin/mysqladmin' -u root password 'new-password'
'/usr/bin/mysqladmin' -u root -h $(hostname) password 'new-password'

Alternatively you can run:
'/usr/bin/mysql_secure_installation'

which will also give you the option of removing the test
databases and anonymous user created by default.  This is
strongly recommended for production servers.

使用你的发行版的初始化系统启动 MariaDB:

$ sudo systemctl start mariadb

在启动时启用 MariaDB 服务器:

$ sudo systemctl enable --now mariadb

现在你已经有了一个 MariaDB 服务器,为它设置一个密码:

mysqladmin -u root password 'myreallysecurepassphrase'
mysqladmin -u root -h $(hostname) password 'myreallysecurepassphrase'

最后,如果你打算在生产服务器上使用它,请在上线前运行 mysql_secure_installation 命令。


via: https://opensource.com/article/20/10/mariadb-mysql-linux

作者:Seth Kenlon 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

减少/缩小逻辑卷是数据损坏的最高风险。

所以,如果可能的话,尽量避免这种情况,但如果没有其他选择的话,那就继续。

缩减 LVM 之前,建议先做一个备份。

当你在 LVM 中的磁盘空间耗尽时,你可以通过缩小现有的没有使用全部空间的 LVM,而不是增加一个新的物理磁盘,在卷组上腾出一些空闲空间。

需要注意的是: 在 GFS2 或者 XFS 文件系统上不支持缩小。

如果你是逻辑卷管理 (LVM) 的新手,我建议你从我们之前的文章开始学习。

减少逻辑卷涉及以下步骤:

  • 卸载文件系统
  • 检查文件系统是否有任何错误
  • 缩小文件系统的大小
  • 缩小逻辑卷的大小
  • 重新检查文件系统是否存在错误(可选)
  • 挂载文件系统
  • 检查减少后的文件系统大小

比如: 你有一个 100GB 的没有使用全部空间的 LVM,你想把它减少到 80GB,这样 20GB 可以用于其他用途。

# df -h /testlvm1

Filesystem              Size Used Avail Use% Mounted on
/dev/mapper/vg01-lv002  100G 15G  85G   12%  /testlvm1

卸载文件系统

使用 umount 命令卸载文件系统:

# umount /testlvm1

检查文件系统是否有任何错误

使用 e2fsck 命令检查文件系统是否有错误:

# e2fsck -f /dev/mapper/vg01-lv002

e2fsck 1.42.9 (28-Dec-2013)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/mapper/vg01-lv002: 13/6553600 files (0.0% non-contiguous), 12231854/26212352 blocks

缩小文件系统

下面的命令将把 testlvm1 文件系统从 100GB 缩小到 80GB

文件系统大小调整的常用语法(resize2fs

resize2fs [现有逻辑卷名] [新的文件系统大小]

实际命令如下:

# resize2fs /dev/mapper/vg01-lv002 80G

resize2fs 1.42.9 (28-Dec-2013)
Resizing the filesystem on /dev/mapper/vg01-lv002 to 28321400 (4k) blocks.
The filesystem on /dev/mapper/vg01-lv002 is now 28321400 blocks long.

减少逻辑卷 (LVM) 容量

现在使用 lvreduce 命令缩小逻辑卷(LVM) 的大小。通过下面的命令, /dev/mapper/vg01-lv002 将把逻辑卷 (LVM) 从 100GB 缩小到 80GB。

LVM 缩减 (lvreduce) 的常用语法

lvreduce [新的 LVM 大小] [现有逻辑卷名称]

实际命令如下:

# lvreduce -L 80G /dev/mapper/vg01-lv002

WARNING: Reducing active logical volume to 80.00 GiB
THIS MAY DESTROY YOUR DATA (filesystem etc.)
Do you really want to reduce lv002? [y/n]: y
Reducing logical volume lv002 to 80.00 GiB
Logical volume lv002 successfully resized

可选:检查文件系统是否有错误

缩减 LVM 后再次检查文件系统是否有错误:

# e2fsck -f /dev/mapper/vg01-lv002

e2fsck 1.42.9 (28-Dec-2013)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/mapper/vg01-lv002: 13/4853600 files (0.0% non-contiguous), 1023185/2021235 blocks

挂载文件系统并检查缩小后的大小

最后挂载文件系统,并检查缩小后的文件系统大小。

使用 mount 命令挂载逻辑卷

# mount /testlvm1

使用 df 命令检查挂载的卷。

# df -h /testlvm1

Filesystem              Size Used Avail Use% Mounted on
/dev/mapper/vg01-lv002  80G  15G  65G   18%  /testlvm1

via: https://www.2daygeek.com/reduce-shrink-decrease-resize-lvm-logical-volume-in-linux/

作者:Magesh Maruthamuthu 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

用 Gemini 协议发现更安静、更简单的互联网新角落。

如果你很久以前就已经上网了,或者是知识非常丰富,你可能还记得一个早期的文本共享协议,叫做 Gopher。Gopher 最终被 HTTP 协议所取代,当然,HTTP 协议是现代万维网的基础。对于很多人来说,“ 互联网 internet ”和“ 万维网 World Wide Web ”是一回事,因为很多人没有意识到在网上进行了 www 子域下的任何操作。

但一直以来,都有各种网络协议在互联网络上共享信息:Telnet、FTP、SSH、Torrent、GNUnet 等等。最近,在这一系列的替代品中又多了一个,它叫 Gemini

Gemini(双子座)协议,以“水星计划”和“阿波罗计划”的基础实验之间的太空任务命名,旨在和平地处在 Gopher 和 HTTP 之间。无论如何,它的目的并不是要取代现代 Web,但它确实试图创造一个简化的网络和一个现代化的 Gopher。

它的发展历史虽然可能很年轻,但意义重大,原因有很多。当然,人们会因为技术和哲学上的原因而对现代 Web 表示质疑,但它只是一般的臃肿。当你真正想要的是一个非常具体的问题的可靠答案时,那么无数次点击谷歌搜索的结果让人感觉过头了。

许多人使用 Gopher 就是因为这个原因:它的规模小到可以让小众的兴趣很容易找到。然而,Gopher 是一个旧的协议,它对编程、网络和浏览做出了一些假设,但这些假设已经不再适用了。 Gemini 的目标是将最好的网络带入一种类似于 Gopher 但易于编程的格式。一个简单的 Gemini 浏览器可以用几百行代码写成,并且有一个非常好的浏览器用 1600 行左右写成。这对于程序员、学生和极简主义者来说都是一个强大的功能。

如何浏览 Gemini

就像早期的网络一样,Gemini 的规模很小,所以有一个列表列出了运行 Gemini 网站的已知服务器。就像浏览 HTTP 站点需要一个网页浏览器一样,访问 Gemini 站点也需要一个 Gemini 浏览器。在 Gemini 网站上列出了有几个可用的浏览器。

最简单的一个是 AV-98 客户端。它是用 Python 编写的,在终端中运行。要想试试的话,请下载它:

$ git clone https://tildegit.org/solderpunk/AV-98.git

进入下载目录,运行 AV-98:

$ cd AV-98.git
$ python3 ./main.py

客户端是一个交互式的提示符。它有有限的几个命令,主要的命令是简单的 go,后面跟着一个 Gemini 服务器地址。在已知的 Gemini 服务器列表中选择一个看起来很有趣的服务器,然后尝试访问它:

AV-98> go gemini://example.club

Welcome to the example.club Gemini server!

Here are some folders of ASCII art:

[1] Penguins
[2] Wildebeests
[3] Demons

导航是按照编号的链接来进行的。例如,要进入 Penguins 目录,输入 1 然后按回车键:

AV-98> 1

[1] Gentoo
[2] Emperor
[3] Little Blue

要返回,输入 back 并按回车键:

AV-98> back

更多命令,请输入 help

用 Gemini 作为你的 web 替代

Gemini 协议非常简单,初级和中级程序员都可以为其编写客户端,而且它是在互联网上分享内容的一种简单快捷的方式。虽然万维网的无处不在对广泛传播是有利的,但总有替代方案的空间。看看 Gemini,发现更安静、更简单的互联网的新角落。


via: https://opensource.com/article/20/10/gemini-internet-protocol

作者:Seth Kenlon 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

本速成教程向你展示了在 Ubuntu 和 Debian Linux 上安装 Yarn 包管理器的官方方法。你还将学习到一些基本的 Yarn 命令以及彻底删除 Yarn 的步骤。

Yarn 是 Facebook 开发的开源 JavaScript 包管理器。它是流行的 npm 包管理器的一个替代品,或者应该说是改进。 Facebook 开发团队 创建 Yarn 是为了克服 npm 的缺点。 Facebook 声称 Yarn 比 npm 更快、更可靠、更安全。

与 npm 一样,Yarn 为你提供一种自动安装、更新、配置和删除从全局注册库中检索到的程序包的方法。

Yarn 的优点是它更快,因为它可以缓存已下载的每个包,所以无需再次下载。它还将操作并行化,以最大化资源利用率。在执行每个已安装的包代码之前,Yarn 还使用 校验和来验证完整性。 Yarn 还保证可以在一个系统上运行的安装,在任何其他系统上都会以完全相同地方式工作。

如果你正 在 Ubuntu 上使用 node.js,那么你的系统上可能已经安装了 npm。在这种情况下,你可以使用 npm 通过以下方式全局安装 Yarn:

sudo npm install yarn -g

不过,我推荐使用官方方式在 Ubuntu/Debian 上安装 Yarn。

在 Ubuntu 和 Debian 上安装 Yarn [官方方式]

这里提到的说明应该适用于所有版本的 Ubuntu,例如 Ubuntu 18.04、16.04 等。同样的一组说明也适用于 Debian 和其他基于 Debian 的发行版。

由于本教程使用 curl 来添加 Yarn 项目的 GPG 密钥,所以最好验证一下你是否已经安装了 curl

sudo apt install curl

如果 curl 尚未安装,则上面的命令将安装它。既然有了 curl,你就可以使用它以如下方式添加 Yarn 项目的 GPG 密钥:

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -

在此之后,将存储库添加到源列表中,以便将来可以轻松地升级 Yarn 包,并进行其余系统更新:

sudo sh -c 'echo "deb https://dl.yarnpkg.com/debian/ stable main" >> /etc/apt/sources.list.d/yarn.list'

你现在可以继续了。更新 Ubuntu 或 Debian 系统,以刷新可用软件包列表,然后安装 Yarn:

sudo apt update
sudo apt install yarn

这将一起安装 Yarn 和 node.js。该过程完成后,请验证是否已成功安装 Yarn。 你可以通过检查 Yarn 版本来做到这一点。

yarn --version

对我来说,它显示了这样的输出:

yarn --version
1.12.3

这意味着我的系统上安装了 Yarn 版本 1.12.3。

使用 Yarn

我假设你对 JavaScript 编程以及依赖项的工作原理有一些基本的了解。我在这里不做详细介绍。我将向你展示一些基本的 Yarn 命令,这些命令将帮助你入门。

使用 Yarn 创建一个新项目

npm 一样,Yarn 也可以使用 package.json 文件。在这里添加依赖项。所有依赖包都缓存在项目根目录下的 node_modules 目录中。

在项目的根目录中,运行以下命令以生成新的 package.json 文件:

它会问你一些问题。你可以按回车键跳过或使用默认值。

yarn init
yarn init v1.12.3
question name (test_yarn): test_yarn_proect
question version (1.0.0): 0.1
question description: Test Yarn
question entry point (index.js):
question repository url:
question author: abhishek
question license (MIT):
question private:
success Saved package.json
Done in 82.42s.

这样,你就得到了一个如下的 package.json 文件:

{
 "name": "test_yarn_proect",
 "version": "0.1",
 "description": "Test Yarn",
 "main": "index.js",
 "author": "abhishek",
 "license": "MIT"
}

现在你有了 package.json,你可以手动编辑它以添加或删除包依赖项,也可以使用 Yarn 命令(首选)。

使用 Yarn 添加依赖项

你可以通过以下方式添加对特定包的依赖关系:

yarn add <包名>

例如,如果你想在项目中使用 Lodash,则可以使用 Yarn 添加它,如下所示:

yarn add lodash
yarn add v1.12.3
info No lockfile found.
[1/4] Resolving packages…
[2/4] Fetching packages…
[3/4] Linking dependencies…
[4/4] Building fresh packages…
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ [email protected]
info All dependencies
└─ [email protected]
Done in 2.67s.

你可以看到,此依赖项已自动添加到 package.json 文件中:

{
 "name": "test_yarn_proect",
 "version": "0.1",
 "description": "Test Yarn",
 "main": "index.js",
 "author": "abhishek",
 "license": "MIT",
 "dependencies": {
 "lodash": "^4.17.11"
 }
}

默认情况下,Yarn 将在依赖项中添加最新版本的包。如果要使用特定版本,可以在添加时指定。

yarn add package@version-or-tag

像往常一样,你也可以手动更新 package.json 文件。

使用 Yarn 升级依赖项

你可以使用以下命令将特定依赖项升级到其最新版本:

yarn upgrade <包名>

它将查看所​​涉及的包是否具有较新的版本,并且会相应地对其进行更新。

你还可以通过以下方式更改已添加的依赖项的版本:

yarn upgrade package_name@version_or_tag

你还可以使用一个命令将项目的所有依赖项升级到它们的最新版本:

yarn upgrade

它将检查所有依赖项的版本,如果有任何较新的版本,则会更新它们。

使用 Yarn 删除依赖项

你可以通过以下方式从项目的依赖项中删除包:

yarn remove <包名>

安装所有项目依赖项

如果对你 project.json 文件进行了任何更改,则应该运行:

yarn

或者,

yarn install

一次安装所有依赖项。

如何从 Ubuntu 或 Debian 中删除 Yarn

我将通过介绍从系统中删除 Yarn 的步骤来完成本教程,如果你使用上述步骤安装 Yarn 的话。如果你意识到不再需要 Yarn 了,则可以将它删除。

使用以下命令删除 Yarn 及其依赖项。

sudo apt purge yarn

你也应该从源列表中把存储库信息一并删除掉:

sudo rm /etc/apt/sources.list.d/yarn.list

下一步删除已添加到受信任密钥的 GPG 密钥是可选的。但要做到这一点,你需要知道密钥。你可以使用 apt-key 命令获得它:

Warning: apt-key output should not be parsed (stdout is not a terminal) pub   rsa4096 2016-10-05 [SC]       72EC F46A 56B4 AD39 C907  BBB7 1646 B01B 86E5 0310 uid           [ unknown] Yarn Packaging [email protected] sub   rsa4096 2016-10-05 [E] sub   rsa4096 2019-01-02 [S] [expires: 2020-02-02]

这里的密钥是以 pub 开始的行中 GPG 密钥指纹的最后 8 个字符。

因此,对于我来说,密钥是 86E50310,我将使用以下命令将其删除:

sudo apt-key del 86E50310

你会在输出中看到 OK,并且 Yarn 包的 GPG 密钥将从系统信任的 GPG 密钥列表中删除。

我希望本教程可以帮助你在 Ubuntu、Debian、Linux Mint、 elementary OS 等操作系统上安装 Yarn。 我提供了一些基本的 Yarn 命令,以帮助你入门,并完成了从系统中删除 Yarn 的完整步骤。

希望你喜欢本教程,如果有任何疑问或建议,请随时在下面留言。


via: https://itsfoss.com/install-yarn-ubuntu

作者:Abhishek Prakash 选题:lujun9972 译者:gxlct008 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出