docker操作脚本
根据容器名称查询容器ID并删除

# 第一种写法 docker stop `docker ps -a| grep project | awk '{print $1}' ` docker rm `docker ps -a| grep project | awk '{print $1}' ` # 第二种写法 docker stop `docker ps -aq --filter name=project` docker rm `docker ps -aq --filter name=project`
根据镜像名称查询容器ID并删除

# 第一种写法 docker stop `docker ps -a| grep yiui/project:1.0.2 | awk '{print $1}' ` docker rm `docker ps -a| grep yiui/project:1.0.2 | awk '{print $1}' ` # 第二种写法 docker stop `docker ps -aq --filter ancestor=yiui/project:1.0` docker rm `docker ps -aq --filter ancestor=yiui/project:1.0`
根据镜像名称查询镜像ID并删除

docker images -q --filter reference=yiui/project*:* docker image rm `docker images -q --filter reference=10.2.21.95:10001/treasury-brain*:*`
判断网络是否存在,不存在进行创建

#! /usr/bin/bash # 定义一个名称变量 network_name="oa_default" filterName=`docker network ls | grep $network_name | awk '{ print $2 }'` if [ "$filterName" == "" ]; then #不存在就创建 docker network create $network_name fi
可参考官方文档
docker拉取

#version内容 #gdd 1.1.3 # 容器名 name=`cat version | awk '{print $1}'` # 容器标签 tag=`cat version | awk '{print $2}'` # 仓库域名 domain=registry.cn-shenzhen.aliyuncs.com # 仓库URL url=colehuang/coletest # 从Docker镜像仓库中拉取镜像 docker login --username=xxx --password=xxx $domain docker pull $domain/$url:$tag # 停止该镜像正在运行的Docker容器 line=`docker ps | grep $name` if [ -n "$line" ]; then echo "存在正在运行的$name容器, 正在使其停止运行..." docker stop $name echo "$name容器, 已停止运行" fi # 删除该镜像的Docker容器 line=`docker ps -a | grep $name` if [ -n "$line" ]; then echo "存在$name容器, 对其进行删除..." docker rm $name echo "$name容器, 已被删除" fi # 启动容器 docker run --rm --name $name -p 8008:8006 -d $domain/$url:$tag IFS=$'\n' # 删除多余镜像 images=`docker images | grep $domain/$url` for i in $images do echo $i t=`echo "$i" | awk '{print $2}'` if [ "$t" != "$tag" ]; then id=`echo "$i" | awk '{print $3}'` docker rmi $id fi done
docker推送

# 容器名 name=`cat version | awk '{print $1}'` # 容器标签 tag=`cat version | awk '{print $2}'` # 仓库域名 domain=registry-vpc.cn-shenzhen.aliyuncs.com # 仓库URL url=colehuang/coletest # 构建Docker镜像 docker build -t $name:$tag . # 获取镜像ID id=`docker images | grep $name | grep $tag | awk '{ print $3 }'` # 镜像上传至Docker镜像仓库 docker login --username=xxx --password=xxx $domain docker tag $id $domain/$url:$tag docker push $domain/$url:$tag
shell脚本
需要可用于远程后台执行的脚本

#方式一 nohup ./main >run.log 2>&1 & 回车 输入exit命令退出终端 关闭shell #方式二 nohup ./main &
重启进程项目
基于shell脚本实现
shell_restart.sh
执行
chmod +x shell_restart.sh
./shell_start.sh 9099

#!/bin/bash if [ -z "$1" ]; then echo "you must input a port" exit 0 fi PID=$(netstat -nlp | grep ":$1" | awk '{print $7}' | awk -F '[ / ]' '{print $1}') if [ "${PID}" != "" ]; then echo "process id is:${PID}" kill -9 "${PID}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "停止旧项目失败" exit 2 fi echo "停止旧项目成功" else echo "没有旧项目" fi #重命名并启动 cp main app chmod +x app nohup ./app >run.log 2>&1 & # shellcheck disable=SC2181 #判断是否启动成功 if [ $? != 0 ]; then echo "新项目启动失败" exit 2 fi echo "新项目启动成功" exit 0
基于docker本地部署实现
版本1
(包含公共网络,重启,删除旧镜像)

#!/bin/bash #接收端口 if [ -z "$1" ]; then echo "you must input a port" exit 0 fi if [ -z "$2" ]; then echo "you must input a image_name" exit 0 fi if [ -z "$3" ]; then echo "you must input a container_name" exit 0 fi image_name=$2 container_name=$3 container_port=$1 #读取镜像tag old_tags=$(docker images --filter=reference="${image_name}" --format "{{.Tag}}") #删除多余镜像 # shellcheck disable=SC2206 array=(${old_tags//," " /}) old_tag="${array[-1]}" # shellcheck disable=SC2068 for i in ${array[@]}; do # shellcheck disable=SC2154 if [ "$i" == "${old_tag}" ]; then continue fi echo "删除镜像" "$i" docker rmi "${image_name}":"${i}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除失败:tag=""${i}" else echo "删除成功:tag=""${i}" fi done echo old_tag="${old_tag}" old_image_id=$(docker images -q --filter reference="${image_name}":"${old_tag}") echo old_image_id="${old_image_id}" if [ "${old_tag}" != "" ]; then tag="${old_tag}" else tag=1 fi # shellcheck disable=SC2034 new_tag=$((tag + 1)) #构建本次镜像 #构建镜像 docker build -t "${image_name}":"${new_tag}" . # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "新构建镜像失败" exit 2 fi echo "构建镜像成功" #关闭上次容器 old_container_id=$(docker ps -aq --filter name="${container_name}") if [ "${old_container_id}" != "" ]; then # shellcheck disable=SC2006 exist=$(docker inspect --format '{{.State.Running}}' "${container_name}") #容器已经被启动了 if [ "${exist}" == "true" ]; then docker stop "${old_container_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "停止旧容器失败" exit 2 fi echo "停止旧容器成功" fi #删除旧容器 if [ "${old_container_id}" != "" ]; then docker rm -f "${old_container_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除旧容器失败" exit 2 fi echo "删除旧容器成功" fi fi #启动本次容器 new_container_id=$(docker run --name "${container_name}" --net=common_net --restart=always -p "${container_port}" -d "${image_name}":"${new_tag}") # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "新容器启动失败" #容器回滚 #关闭本次容器 if [ "${new_container_id}" != "" ]; then docker rm -f "${new_container_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除新容器失败" exit 2 fi echo "删除新容器成功" fi #删除新镜像 new_image_id=$(docker images -q --filter reference="${image_name}":"${new_tag}") if [ "${new_image_id}" != "" ]; then docker rmi -f "${new_image_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除新镜像失败" exit 2 fi echo "删除新镜像成功" fi if [ "${old_image_id}" != "" ]; then #启动上次容器 docker run --name "${container_name}" --net=common_net --restart=always -p "${container_port}" -d "${image_name}":"${old_tag}" if [ $? != 0 ]; then echo "容器回滚失败" exit 2 else echo "容器回滚成功" fi else echo "没有上个版本镜像不能回滚" exit 2 fi else echo "新容器启动成功" #删除旧镜像 echo old_image_id="${old_image_id}" if [ "${old_image_id}" != "" ]; then docker rmi -f "${old_image_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除旧镜像失败" exit 2 fi echo "删除旧镜像成功" else echo "没有旧镜像" fi fi
版本2
默认使用外部网络common_net,默认重启
- chmod +x docker_iter.sh
#端口,镜像 容器
- ./docker_iter.sh 9081:9081 yiui/shoptool shoptool

#!/bin/bash #接收端口 if [ -z "$1" ]; then echo "you must input a port" exit 0 fi if [ -z "$2" ]; then echo "you must input a image_name" exit 0 fi if [ -z "$3" ]; then echo "you must input a container_name" exit 0 fi image_name=$2 container_name=$3 container_port=$1 #读取镜像tag old_tags=$(docker images --filter=reference="${image_name}" --format "{{.Tag}}") #删除多余镜像 # shellcheck disable=SC2206 array=(${old_tags//," " /}) old_tag="${array[-1]}" # shellcheck disable=SC2068 for i in ${array[@]}; do # shellcheck disable=SC2154 if [ "$i" == "${old_tag}" ]; then continue fi echo "删除镜像" "$i" docker rmi "${image_name}":"${i}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除失败:tag=""${i}" else echo "删除成功:tag=""${i}" fi done echo old_tag="${old_tag}" old_image_id=$(docker images -q --filter reference="${image_name}":"${old_tag}") echo old_image_id="${old_image_id}" if [ "${old_tag}" != "" ]; then tag="${old_tag}" else tag=1 fi # shellcheck disable=SC2034 new_tag=$((tag + 1)) #构建本次镜像 #构建镜像 docker build -t "${image_name}":"${new_tag}" . # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "新构建镜像失败" exit 2 fi echo "构建镜像成功" #关闭上次容器 old_container_id=$(docker ps -aq --filter name="${container_name}") if [ "${old_container_id}" != "" ]; then # shellcheck disable=SC2006 exist=$(docker inspect --format '{{.State.Running}}' "${container_name}") #容器已经被启动了 if [ "${exist}" == "true" ]; then docker stop "${old_container_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "停止旧容器失败" exit 2 fi echo "停止旧容器成功" fi #删除旧容器 if [ "${old_container_id}" != "" ]; then docker rm -f "${old_container_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除旧容器失败" exit 2 fi echo "删除旧容器成功" fi fi #启动本次容器 new_container_id=$(docker run --name "${container_name}" --net=common_net --restart=always -p "${container_port}" -d "${image_name}":"${new_tag}") # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "新容器启动失败" #容器回滚 #关闭本次容器 if [ "${new_container_id}" != "" ]; then docker rm -f "${new_container_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除新容器失败" exit 2 fi echo "删除新容器成功" fi #删除新镜像 new_image_id=$(docker images -q --filter reference="${image_name}":"${new_tag}") if [ "${new_image_id}" != "" ]; then docker rmi -f "${new_image_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除新镜像失败" exit 2 fi echo "删除新镜像成功" fi if [ "${old_image_id}" != "" ]; then #启动上次容器 docker run --name "${container_name}" --net=common_net --restart=always -p "${container_port}" -d "${image_name}":"${old_tag}" if [ $? != 0 ]; then echo "容器回滚失败" exit 2 else echo "容器回滚成功" fi else echo "没有上个版本镜像不能回滚" exit 2 fi else echo "新容器启动成功" #删除旧镜像 echo old_image_id="${old_image_id}" if [ "${old_image_id}" != "" ]; then docker rmi -f "${old_image_id}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "删除旧镜像失败" exit 2 fi echo "删除旧镜像成功" else echo "没有旧镜像" fi fi
基于docker阿里云远程部署实现
基于docker本地仓库部署实现
java项目shell脚本部署
./shell_start.sh 9031

#!/bin/bash if [ -z "$1" ]; then echo "you must input a port" exit 0 fi PID=$(netstat -nlp | grep ":$1" | awk '{print $7}' | awk -F '[ / ]' '{print $1}') if [ "${PID}" != "" ]; then echo "process id is:${PID}" kill -9 "${PID}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "停止旧项目失败" exit 2 fi echo "停止旧项目成功" else echo "没有旧项目" fi #重命名并启动 cp target/pisces-system-1.0.0.jar target/app.jar chmod +x target/* nohup java -jar target/app.jar >run.log 2>&1 & # shellcheck disable=SC2181 #判断是否启动成功 if [ $? != 0 ]; then echo "新项目启动失败" exit 2 fi echo "新项目启动成功" exit 0
shell_iter.sh
./shell_iter.sh 9031 target/pisces-system-1.0.0.jar

#!/bin/bash if [ -z "$1" ]; then echo "you must input a port" exit 0 fi if [ -z "$2" ]; then echo "you must input a exec file" exit 0 fi #PID=$(netstat -nlp | grep ":$1" | awk '{print $7}' | awk -F '[ / ]' '{print $1}') #读取pid PID=$(cat ./shaw-test-web.pid) if [ "${PID}" != "" ]; then echo "process id is:${PID}" kill -9 "${PID}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "停止旧项目失败" exit 2 fi echo "停止旧项目成功" else echo "没有旧项目" fi #重命名并启动 cp "${2}" app.jar chmod +x app.jar nohup java -jar app.jar --spring.profiles.active=prod >run.log 2>&1 & # shellcheck disable=SC2181 echo $! >./shaw-test-web.pid #判断是否启动成功 if [ $? != 0 ]; then echo "新项目启动失败" exit 2 fi echo "新项目启动成功" exit 0
nginx脚本
nginx+dockerfile代理部署
Dockerfile

FROM nginx:latest WORKDIR /dist COPY . . COPY build/ /usr/share/nginx/html/ # COPY default.conf /etc/nginx/conf.d/default.conf EXPOSE 6001
default.conf

server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html index.htm; location /tool/ { proxy_set_header Host $host; #保留代理之前的host proxy_set_header X-Real-IP $remote_addr; #保留代理之前的真实客户端ip proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header HTTP_X_FORWARDED_FOR $remote_addr; #在多级代理的情况下,记录每次代理之前的客户端真实ip # limit_req zone=myRateLimit burst=20 nodelay; proxy_pass http://192.168.10.231:9081/; proxy_redirect default; #指定修改被代理服务器返回的响应头中的location头域跟refresh头域数值 } #DENY_FILES_START location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md) { return 403; } #DENY_FILES_END location ~ .+\.(gif|jpg|jpeg|png|bmp|swf)$ { expires 1d; error_log off; access_log off; } location ~ .+\.(js|css)$ { expires 1h; error_log off; access_log off; } location = /favicon.ico { log_not_found off; access_log off; } access_log logs/access.log; error_log logs/error.log; }
配置文件处理(替换路由)
server='http://192.168.10.231:9033' sed -i "s#baseURL[^,]*#baseURL: \'$server\'#g" src/request/axios.conf.ts
shell脚本作为用户变量使用
java项目docker部署脚本
自动部署

#!/bin/bash #docker_iter.sh aquarius-api 9023 prod if [ -z "$1" ]; then echo "you must input a projectName" exit 0 fi if [ -z "$2" ]; then echo "you must input a port" exit 0 fi if [ -z "$3" ]; then echo "you must input a envType" exit 0 fi dockerhub_url="registry.cn-hangzhou.aliyuncs.com" namespace="hongyi_repository" projectName=$1 port=$2 envType=$3 imageName=$dockerhub_url/$namespace/$projectName containerIdOld=$(docker ps -a | grep -w "${projectName}" | awk '{print $1}') imageNameOld=$(docker ps -a | grep -w "${projectName}" | awk '{print $2}') #读取镜像tag # shellcheck disable=SC2034 tagOld=$(docker images --filter=reference="${imageNameOld}" --format "{{.Tag}}") # shellcheck disable=SC2034 tagNew=$((tagOld + 1)) echo "打印新tag" echo $tagNew #构建本次镜像 # shellcheck disable=SC2154 imageNameNew=${imageName}:${tagNew} echo "$imageNameNew" #构建镜像 # shellcheck disable=SC2154 docker build -t "${imageNameNew}" --build-arg envType="${envType}" . # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "新构建镜像失败" exit 2 fi echo "构建镜像成功" #关闭上次容器 containerIdOld=$(docker ps -aq --filter name="${projectName}") if [ "$containerIdOld" != "" ]; then #停掉容器 docker stop "$containerIdOld" #删除容器 # shellcheck disable=SC2086 docker rm $containerIdOld echo "停止删除旧容器成功" else echo "旧容器不存在" fi #启动本次容器 # 启动新容器 # --net=common_net --restart=always docker run -di --name "$projectName" --net=common_net --restart=always -p "$port":"$port" "$imageNameNew" # shellcheck disable=SC2181 if [ $? == 0 ]; then echo "新容器启动成功" if [ "$imageNameOld" != "" ]; then docker rmi -f "$imageNameOld" if [ $? == 0 ]; then echo "删除旧镜像成功" else echo "删除旧镜像失败" fi exit 0 else echo "旧镜像不存在" fi else echo "新容器启动失败" containerIdNew=$(docker ps -a | grep -w "${projectName}" | awk '{print $1}') #删除新容器 if [ "$containerIdNew" != "" ]; then #停掉容器 docker stop "$containerIdNew" docker rm "$containerIdNew" echo "停止删除新容器成功" #启动旧容器 # 启动新容器 # --net=common_net --restart=always docker run -di --name "$projectName" --net=common_net --restart=always -p "$port":"$port" "$imageNameOld" if [ $? == 0 ]; then echo "部署回滚成功" else echo "部署回滚失败" exit 1 fi #删除旧镜像 docker rmi -f "$imageNameNew" if [ $? == 0 ]; then echo "删除新镜像成功" else echo "删除新镜像失败" fi fi fi
手动发布

#! /bin/sh #docker_publish.sh aquarius-system hongyi_repository 33 aquarius-system prod #docker tag 37bb9c63c8b2 registry-vpc.cn-hangzhou.aliyuncs.com/acs/agent:0.7-dfb6816 projectName=$1 if [ -z "$projectName" ]; then echo "you must input a projectName" exit 1 fi tag=$2 if [ -z "$tag" ]; then echo "you must input a tag" exit 1 fi envType=$3 if [ -z "$envType" ]; then echo "you must input a envType" exit 1 fi dockerhub_url="registry.cn-hangzhou.aliyuncs.com" namespace="hongyi_repository" #9023 #docker login -u 用户名 -p 密码 192.168.2.6:85 #docker pull 192.168.2.6:85/tensquare/eureka:v1 imageName=$dockerhub_url/$namespace/$projectName:$tag # shellcheck disable=SC2154 docker build -t "${imageName}" --build-arg envType="${envType}" . # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "镜像构建失败" exit 1 else echo "镜像构建成功" fi # 登录Harbor #docker login --username=虹蚁科技 --password=docker.hongyi registry.cn-hangzhou.aliyuncs.com docker login --username=虹蚁科技 --password=docker.hongyi "$dockerhub_url" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "登录失败" exit 1 else echo "登录成功" fi echo "------------------------------开始推送${imageName}到仓库------------------------------" docker push "${imageName}" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "${imageName}推送失败" exit 1 else echo "${imageName}推送成功" exit 0 fi
手动部署

#! /bin/sh #docker_deploy.sh registry.cn-hangzhou.aliyuncs.com hongyi_repository aquarius-system "${tag}" 9021 #接收外部参数 dockerhub_url=$1 if [ -z "$dockerhub_url" ]; then echo "you must input a dockerhub_url" exit 1 fi #192.168.10.119:8087 #命名空间 namespace=$2 if [ -z "$namespace" ]; then echo "you must input a namespace" exit 1 fi #aquarius projectName=$3 if [ -z "$projectName" ]; then echo "you must input a project_name" exit 1 fi #aquarius-api tag=$4 if [ -z "$tag" ]; then echo "you must input a tag" exit 1 fi #3 port=$5 if [ -z "$port" ]; then echo "you must input a port" exit 1 fi #9023 #docker login -u 用户名 -p 密码 192.168.2.6:85 #docker pull 192.168.2.6:85/tensquare/eureka:v1 imageNameNew=$dockerhub_url/$namespace/$projectName:$tag echo "$imageNameNew" containerIdOld=$(docker ps -a | grep -w "${projectName}" | awk '{print $1}') imageNameOld=$(docker ps -a | grep -w "${projectName}" | awk '{print $2}') #读取镜像tag # shellcheck disable=SC2034 tagOld=$(docker images --filter=reference="${imageNameOld}" --format "{{.Tag}}") # # shellcheck disable=SC2039 if [ "$tagOld" == "$tag" ]; then echo "该镜像已部署,不可重复部署" exit 1 fi #拉取新镜像 # 登录Harbor #docker login --username=虹蚁科技 --password=docker.hongyi registry.cn-hangzhou.aliyuncs.com docker login --username=虹蚁科技 --password=docker.hongyi "$dockerhub_url" # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "登录失败" exit 1 else echo "登录成功" fi # 下载镜像 #docker pull registry.cn-hangzhou.aliyuncs.com/hongyi_repository/aquarius-api:latest echo "开始拉取镜像" docker pull "$imageNameNew" # shellcheck disable=SC2181 if [ $? != 0 ]; then # shellcheck disable=SC2016 echo "镜像${imageNameNew}拉取失败" exit 1 else echo "镜像${imageNameNew}拉取成功" fi #停止删除,旧容器 if [ "$containerIdOld" != "" ]; then #停掉容器 docker stop "$containerIdOld" #删除容器 # shellcheck disable=SC2086 docker rm $containerIdOld echo "停止删除旧容器成功" else echo "旧容器不存在" fi # 启动新容器 # --net=common_net --restart=always docker run -di --name "$projectName" --net=common_net --restart=always -p "$port":"$port" "$imageNameNew" # shellcheck disable=SC2181 if [ $? == 0 ]; then echo "新容器启动成功" if [ "$imageNameOld" != "" ]; then docker rmi -f "$imageNameOld" if [ $? == 0 ]; then echo "删除旧镜像成功" else echo "删除旧镜像失败" fi exit 0 else echo "旧镜像不存在" fi else echo "新容器启动失败" containerIdNew=$(docker ps -a | grep -w "${projectName}" | awk '{print $1}') #删除新容器 if [ "$containerIdNew" != "" ]; then #停掉容器 docker stop "$containerIdNew" docker rm "$containerIdNew" echo "停止删除新容器成功" #启动旧容器 # 启动新容器 # --net=common_net --restart=always docker run -di --name "$projectName" --net=common_net --restart=always -p "$port":"$port" "$imageNameOld" if [ $? == 0 ]; then echo "部署回滚成功" else echo "部署回滚失败" exit 1 fi #删除旧镜像 docker rmi -f "$imageNameNew" if [ $? == 0 ]; then echo "删除新镜像成功" else echo "删除新镜像失败" fi fi fi #echo "------------打印日志中------------" #sleep 60 #containerId=$(docker ps -a | grep -w "${project_name}" | awk '{print $1}') # #docker logs -t "$containerId" # #echo "------------打印日志结束------------" #module='' #调用脚本 #if [ "$module" == "aquarius-system" ]; then # echo "部署管理后台服务模块" # docker_deploy.sh registry.cn-hangzhou.aliyuncs.com hongyi_repository aquarius-system "${tag}" 9021 #elif [ "$module" == "aquarius-api" ]; then # echo "部署客户服务模块" # docker_deploy.sh registry.cn-hangzhou.aliyuncs.com hongyi_repository aquarius-api "${tag}" 9023 #else # echo "模块不存在" #fi
Dockerfile

# 基础镜像使用java FROM java:8 # 作者 MAINTAINER fengyun #docker build -t "${imageNameNew}" --build-arg envType="${envType}" . ARG envType # 将jar包添加到容器中并更名为app.jar COPY aquarius-api-1.0.0.jar app.jar RUN echo ${envType} EXPOSE 9023 # 运行jar包 ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=$envType" ]