跳转到内容

Bourne Shell 脚本/附录 D: 食谱

来自维基教科书,开放的书籍,开放的世界
维基共享资源徽标 发布新的食谱条目
如果您使用标题框,则无需在正文中放置标题。

基于扩展名分支

[编辑 | 编辑源代码]

在编写应根据文件扩展名执行不同操作的 bash 脚本时,以下模式很有用。

 #filepath should be set to the name(with optional path) of the file in question
 ext=${filepath##*.}
 if [[ "$ext" == txt ]] ; then 
   #do something with text files
 fi

(来源:slike.com Bash FAQ).

重命名多个文件

[编辑 | 编辑源代码]

此食谱演示了如何按照模式重命名多个文件。

在这个例子中,用户有大量的屏幕截图。该用户想要使用 Bourne 兼容的 shell 重命名这些文件。这是一个在 shell 提示符下显示文件名的 "ls"。目标是将像 "snapshot1.png" 这样的图像重命名为 "nethack-kernigh-22oct2005-01.png"

 $ ls
 snapshot1.png   snapshot25.png  snapshot40.png  snapshot56.png  snapshot71.png
 snapshot10.png  snapshot26.png  snapshot41.png  snapshot57.png  snapshot72.png 
 snapshot11.png  snapshot27.png  snapshot42.png  snapshot58.png  snapshot73.png
 snapshot12.png  snapshot28.png  snapshot43.png  snapshot59.png  snapshot74.png
 snapshot13.png  snapshot29.png  snapshot44.png  snapshot6.png   snapshot75.png
 snapshot14.png  snapshot3.png   snapshot45.png  snapshot60.png  snapshot76.png
 snapshot15.png  snapshot30.png  snapshot46.png  snapshot61.png  snapshot77.png
 snapshot16.png  snapshot31.png  snapshot47.png  snapshot62.png  snapshot78.png
 snapshot17.png  snapshot32.png  snapshot48.png  snapshot63.png  snapshot79.png
 snapshot18.png  snapshot33.png  snapshot49.png  snapshot64.png  snapshot8.png
 snapshot19.png  snapshot34.png  snapshot5.png   snapshot65.png  snapshot80.png
 snapshot2.png   snapshot35.png  snapshot50.png  snapshot66.png  snapshot81.png
 snapshot20.png  snapshot36.png  snapshot51.png  snapshot67.png  snapshot82.png
 snapshot21.png  snapshot37.png  snapshot52.png  snapshot68.png  snapshot83.png
 snapshot22.png  snapshot38.png  snapshot53.png  snapshot69.png  snapshot9.png
 snapshot23.png  snapshot39.png  snapshot54.png  snapshot7.png
 snapshot24.png  snapshot4.png   snapshot55.png  snapshot70.png

首先,要在快照 1 到 9 之前添加 "0"(零),编写一个 for 循环(实际上是一个简短的 shell 脚本)。

  • 使用?这是一个单个字符的文件名模式。使用它,我可以匹配快照 1 到 9,但可以通过说来错过 10 到 83snapshot?.png.
  • 使用${参数#模式}用从开头删除的模式替换参数的值。这是为了去掉 "snapshot",以便我可以放入 "snapshot0"。
  • 在实际运行循环之前,插入一个 "echo" 来测试命令是否正确。
 $ for i in snapshot?.png; do echo mv "$i" "snapshot0${i#snapshot}"; done
 mv snapshot1.png snapshot01.png
 mv snapshot2.png snapshot02.png
 mv snapshot3.png snapshot03.png
 mv snapshot4.png snapshot04.png
 mv snapshot5.png snapshot05.png
 mv snapshot6.png snapshot06.png
 mv snapshot7.png snapshot07.png
 mv snapshot8.png snapshot08.png
 mv snapshot9.png snapshot09.png

看起来不错,所以通过删除 "echo" 来运行它。

 $ for i in snapshot?.png; do mv "$i" "snapshot0${i#snapshot}"; done

一个 ls 证实了这很有效。

现在将前缀 "snapshot" 更改为 "nethack-kernigh-22oct2005-"。运行一个类似于上一个循环的循环

 $ for i in snapshot*.png; do \
 > mv "$i" "nethack-kernigh-22oct2005-${i#snapshot}" \
 > done

这为用户节省了键入 83 个 "mv" 命令的麻烦。

长命令行选项

[编辑 | 编辑源代码]

内置的getopts不支持长选项,因此需要外部的getopt是必需的。(在某些系统上,getopt 不支持长选项,因此下一个示例将无法正常工作。)

eval set -- $(getopt -l install-opts: "" "$@")
while true; do
    case "$1" in
        --install-opts)
            INSTALL_OPTS=$2
            shift 2
            ;;
        --)
            shift
            break
            ;;
    esac
done

echo $INSTALL_OPTS

getopt引用并重新排序在$@. set中找到的命令行参数,然后用$@的输出替换getopt

高级 Bash 脚本指南中也可以找到 getopt 使用的另一个示例

通过 xargs 处理特定文件

[编辑 | 编辑源代码]

在这个食谱中,我们想要处理一个大型文件列表,但我们必须为每个文件运行一个命令。在这个例子中,我们想要将一些声音文件的采样率转换为 44100 赫兹。命令是sox file.ogg -r 44100 conv/file.ogg,它将file.ogg转换为一个新文件conv/file.ogg。我们还想要跳过已经为 44100 赫兹的文件。

首先,我们需要我们文件的采样率。一种方法是使用file命令

 $ file *.ogg
 audio_on.ogg:            Ogg data, Vorbis audio, mono, 44100 Hz, ~80000 bps
 beep_1.ogg:              Ogg data, Vorbis audio, stereo, 44100 Hz, ~193603 bps
 cannon_1.ogg:            Ogg data, Vorbis audio, mono, 48000 Hz, ~96000 bps
 ...

(此示例中的文件来自 秘密玛丽奥编年史。) 我们可以使用grep -v过滤掉所有包含 '44100 Hz' 的行

 $ file *.ogg | grep -v '44100 Hz'
 cannon_1.ogg:            Ogg data, Vorbis audio, mono, 48000 Hz, ~96000 bps
 ...
 jump_small.ogg:          Ogg data, Vorbis audio, mono, 8000 Hz, ~22400 bps
 live_up.ogg:             Ogg data, Vorbis audio, mono, 22050 Hz, ~40222 bps
 ...

我们使用 "grep" 和 "file" 完成了,所以现在我们想要删除其他信息,只留下要传递给 "sox" 的文件名。我们使用文本实用程序cut。选项-d将每行在冒号处分成字段;-f1选择第一个字段。

 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1
 cannon_1.ogg
 ...
 jump_small.ogg
 live_up.ogg
 ...

我们可以使用另一个管道来提供标准输入上的文件名,但 "sox" 将它们作为参数。我们使用xargs,它将使用来自标准输入的参数重复运行命令。该-n1选项指定每个命令一个参数。例如,我们可以运行echo sox重复

 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1 | xargs -n1 echo sox
 sox cannon_1.ogg
 ...
 sox itembox_set.ogg
 sox jump_small.ogg
 ...

但是,这些命令是错误的。例如,cannon_1.ogg 的完整命令是sox cannon_1.ogg -r 44100 conv/cannon_1.ogg。"xargs" 将插入传入数据到由 " {} " 表示的占位符中。我们在此管道中使用此策略。如果有疑问,首先我们可以使用 "echo" 构建一个测试管道

 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1 | \
 > xargs -i 'echo sox {} -r 44100 conv/{}'
 sox cannon_1.ogg -r 44100 conv/cannon_1.ogg
 ...
 sox itembox_set.ogg -r 44100 conv/itembox_set.ogg
 sox jump_small.ogg -r 44100 conv/jump_small.ogg
 ...

它起作用了,所以让我们删除 "echo" 并运行 "sox" 命令

 $ mkdir conv
 $ file *.ogg | grep -v '44100 Hz' | cut -d: -f1 | \
 > xargs -i 'sox {} -r 44100 conv/{}'

等待一段时间后,转换后的文件将出现在conv子目录中。以上三行代码单独完成了整个转换。

GStreamer 的简单播放列表前端

[编辑 | 编辑源代码]

如果您有 GStreamer,则命令gst-launch filesrc location=filename ! decodebin ! audioconvert ! esdsink将播放任何格式的声音或音乐文件,只要您拥有 GStreamer 插件。此脚本将播放文件列表,可以选择循环播放它们。(将 "esdsink" 替换为您最喜欢的接收器。)

#!/bin/sh
loop=false
if test x"$1" == x-l; then
  loop=true
  shift
fi

while true; do
  for i in "$@"; do
    if test -f "$i"; then
      echo "${0##*/}: playing $i" > /dev/stderr
      gst-launch filesrc location="$i" ! decodebin ! audioconvert ! esdsink
    else
      echo "${0##*/}: not a file: $i" > /dev/stderr
    fi
  done
  if $loop; then true; else break; fi
done

此脚本演示了一些常见的 Bourne shell 策略

  • "loop" 是一个布尔变量。它之所以有效是因为它的值 "true" 和 "false" 都是 Unix 命令(有时也是 shell 内置命令),因此您可以在ifwhile语句中使用它们作为条件。
  • shell 内置命令 "shift" 从参数列表中删除 $1,从而将 $2 移到 $1,将 $3 移到 $2,等等。此脚本使用它来处理 "-l" 选项。
  • 替换${0##*/}提供 $0 中最后一个斜线后的所有内容,因此是 "playlist",而不是 "/home/musicfan/bin/playlist"。


下一页: Bourne Shell 脚本 | 上一页: 附录 C: 快速参考
首页: Bourne Shell 脚本
华夏公益教科书