命令解析

如何用shell来做一个命令行工具。最简单的,可能是判断用户的输入 $?,另外还有 getopt函数。本文收集了多个shell的命令行解析方式。

脚本首行的,set -o errexit不确定是什么作用。

set

-e 脚本中的命令一旦运行失败就终止脚本的执行
-x 用于显示出命令与其执行结果(默认shell脚本中只显示执行结果)

#!/bin/bash
set -x
echo "Hello World !"

效果就是,执行一行,输出当前正在执行的命令。 其实效果和sh -x xx.sh一样会打印执行过程

执行效果为:

+ echo Hello World !
Hello World !

set -e 遇到错误就终止执行。

FROM nginx:1.19
RUN set -ex \
    && rm -rf /etc/nginx/conf.d/default.conf

case + shift

示例1

以下示例,演示如何在shell中解析命令,例子来源istio-1.9.3提供的示例代码。*)可以完善为提示工具用法。

#!/bin/bash
set -euo pipefail

INCLUDE_SERVICE=${INCLUDE_SERVICE:-"true"}
INCLUDE_DEPLOYMENT=${INCLUDE_DEPLOYMENT:-"true"}
SERVICE_VERSION=${SERVICE_VERSION:-"v1"}
while (( "$#" )); do
  case "$1" in
    --version)
      SERVICE_VERSION=$2
      shift 2
      ;;

    --includeService)
      INCLUDE_SERVICE=$2
      shift 2
      ;;

    --includeDeployment)
      INCLUDE_DEPLOYMENT=$2
      shift 2
      ;;

    *)
      echo "Error: Unsupported flag $1" >&2
      exit 1
      ;;
  esac
done

SERVICE_YAML=$(cat <<EOF
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
    service: helloworld
spec:
  ports:
  - port: 5000
    name: http
  selector:
    app: helloworld
EOF
)

DEPLOYMENT_YAML=$(cat <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld-${SERVICE_VERSION}
  labels:
    app: helloworld
    version: ${SERVICE_VERSION}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld
      version: ${SERVICE_VERSION}
  template:
    metadata:
      labels:
        app: helloworld
        version: ${SERVICE_VERSION}
    spec:
      containers:
      - name: helloworld
        env:
        - name: SERVICE_VERSION
          value: ${SERVICE_VERSION}
        image: docker.io/istio/examples-helloworld-v1
        resources:
          requests:
            cpu: "100m"
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5000
EOF
)

OUT=""

# Add the service to the output.
if [[ "$INCLUDE_SERVICE" == "true" ]]; then
  OUT="${SERVICE_YAML}"
fi

# Add the deployment to the output.
if [[ "$INCLUDE_DEPLOYMENT" == "true" ]]; then
  # Add a separator
  if [[ -n "$OUT" ]]; then
    OUT+="
---
"
  fi
  OUT+="${DEPLOYMENT_YAML}"
fi

echo "$OUT"

示例2

下面这个例子,使用了$@,函数,等功能。

#!/usr/bin/env bash

# start containers
function up {
  docker-compose up -d $@
}

# stop containers
function down {
  docker-compose down
}

# initialize application
function init {
  echo "Stating Initialization..."

  echo "Copy .env file..."
  cp .env.example .env

  echo "Install PHP dependencies..."
  docker-compose run roadrunner sh -c 'composer require spiral/roadrunner:${RR_VERSION}'

  echo "Initialization completed!"
}

# login to container
function login {
  container=${1:-roadrunner}

  echo "Attempt to login ${container} container..."
  docker-compose exec ${container} bash
}

# show container logs
function logs {
  docker-compose logs $1
}

# execute RoadRunner command
function rr {
  docker-compose exec roadrunner rr -c /etc/rr.yaml $1
}

subcommand=$1
shift

case $subcommand in
up)
  up $@
  ;;
down)
  down
  ;;
init)
  init
  ;;
login)
  login $1
  ;;
logs)
  logs $1
  ;;
rr)
  rr $1
  ;;
*)
  echo "help"
  ;;
esac

为了演示上面的用到的一些功能,我写了如下一个例子。最终,使用bash cmd.sh zhang shuo ai wo 来调用一下。(所以,我们发现,脚本在函数功,还支持默认参数功能。)

#!/bin/bash
# 函数内的,只能显示,调用该函数的参数,而不是脚本传入的参数 。
function hello {
   echo $#
   echo $1
   echo $2
   echo $3
   echo $@ 
}

function init {
   #msg=${1:-defaultmsg}  # 相当于指定了默认参数。
   msg=${1:-defaultmsg some}
   echo $msg
}

echo $@
shift  # 该参数,会移除传入脚本的第1个参数,前后两个命令正好对比。
echo $@

hello  liu xiao ming

# init 必须要先定义才能使用
init
init custom_msg

示例3

echo -e "a\nb\nc" 是可以支持使用转义符号的。

shell里面直接调用另外一个脚本,(把它想象成,在shell中执行命令,绝对路径访问即可)

set -o errexit
display_usage() {
    echo
    echo "USAGE: ./build_push_update_images.sh <version> [-h|--help] [--prefix=value] [--scan-images]"
    echo "	version : Version of the sample app images (Required)"
    echo "	-h|--help : Prints usage information"
    echo "	--prefix: Use the value as the prefix for image names. By default, 'istio' is used"
    echo -e "	--scan-images : Enable security vulnerability scans for docker images \n\t\t\trelated to bookinfo sample apps. By default, this feature \n\t\t\tis disabled."
    exit 1
}

# Check if there is atleast one input argument
if [[ -z "$1" ]] ; then
	echo "Missing version parameter"
        display_usage
else
	VERSION="$1"
	shift
fi

# Process the input arguments. By default, image scanning is disabled.
PREFIX=istio
ENABLE_IMAGE_SCAN=false
echo "$@"
for i in "$@"
do
	case "$i" in
		--prefix=* )
		   PREFIX="${i#--prefix=}" ;;
		--scan-images )
		   ENABLE_IMAGE_SCAN=true ;;
		-h|--help )
		   echo
		   echo "Build the docker images for bookinfo sample apps, push them to docker hub and update the yaml files."
		   display_usage ;;
		* )
		   echo "Unknown argument: $i"
		   display_usage ;;
	esac
done

#Build docker images  直接调用,另外一个脚本
src/build-services.sh "${VERSION}" "${PREFIX}"

getopts

示例1

#!/bin/bash


usage () {
	echo -e "\033[31mIMPORTANT: Run As Root\033[0m"
	echo ""
	echo "Usage:	docker.sh [OPTIONS]"
	echo ""
	echo "A docker written by shell"
	echo ""
	echo "Options:"
	echo "		-c string	docker command"
	echo "				(\"run\")"
	echo "		-m		memory"
	echo "				(\"100M, 200M, 300M...\")"
	echo "		-C string	container name"
	echo "		-I string	image name"
	echo "		-V string	volume"
	echo "		-P string	program to run in container"

	return 0
}

if test "$(whoami)" != root
then
	usage
	exit -1
fi

while getopts c:m:C:I:V:P: option
do
	case "$option"
	in
		c) cmd=$OPTARG;;
		m) memory=$OPTARG;;
		C) container=$OPTARG;;
		I) image=$OPTARG;;
		V) volume=$OPTARG;;
		P) program=$OPTARG;;
		\?) usage
		    exit -2;;
	esac
done

export cmd=$cmd
export memory=$memory
export container=$container
export image=$image
export volume=$volume
export program=$program

unshare --uts --mount --pid --fork ./container.sh