记录使用expect遇到的一个坑

记录使用 expect 遇到的一个坑


1. 问题描述

最近写了一个自动化的安装程序,由于要远程操作多台服务器进行安装,所以必然想到了使用 Shell 脚本来自动化。而设计到密码输入的(比如 sudo )则使用 expect 来进行交互,然后被一个小坑浪费了一个下午的时间。

先上代码:

#!/bin/bash
user=${1}
ip=${2}
passwd=${3}


expect -c "
spawn ssh ${user}@${ip} -t 'uname -a'
expect {
    assword: {
        send \"${passwd}\r\"
    }
}
expect eof
"

本质上就是连接到服务器执行命令而已,uname -a 但是脚本竟然报错了:zsh:1: command not found: uname -a(注:真实执行的命令可不是 uname -a 这么简单,这里做示例足够了)。

WTF?

2. 解决过程

想了半天,我一直以为是使用 ssh 登录过去环境变量不对导致的。花了半天时间研究了ssh连接远程主机执行脚本的环境变量问题 这篇文章。虽然没有解决问题,但是文章不错!

苦恼良久,我看到了 send \"${passwd}\r\" 这段代码感到很奇怪。这段 expect 代码是从一个前同事的脚本中扒下来的,整条 expect 命令是由双引号括起来的,字符串参数值替换应该没问题,为什么一定要用转义的双引号呢?

我猜这里可能有什么关键,所以果断把 'uname -a' 改成 \"uname -a\"

结果跑通了!

接下来我又发现 'uname -a' 改成 'uname' 后原来的代码也能跑。

测试了一下午加一个晚上总结出,总结出三种可以跑通的情况:

  1. 不在 expect 命令中而直接以 ssh <user>@<ip> <cmd> 的形式调用。
  2. 'uname -a' 改成 \"uname -a\"
  3. 'uname -a' 改成 'uname'

所以我猜测应该是在使用 expect 时命令体内部在表示 <cmd> 部分出了问题。当命令带 flag 时,单引号没有起到作用,需要使用转义的双引号。

其底层原因我也没时间去探索了,在此 mark 一下吧。

3. 结论

总结来说,expect 命令内部需要用双引号和单引号的还是尽量用转义的双引号吧!

使用 Shell 变量的正确正确姿势

今天发生了在写 Shell脚本的时候发现了一个问题:变量赋值了却取不出来!

当时的代码是这么写的:

V_TS=`date +%Y%m%d`
TABLE_NAME=dwm_xxx_xxx_xxx_metric_day
TEMP_TABLE="es_$TABLE_NAME_$V_TS"

本意是原来的表名加一个前缀 es,加一个后缀日期。结果 TEMP_TABLE 赋值得到的结果是 es_20160303 !!!

仔细一分析,原来在这里把变量名 TABLE_NAME 识别成了 TABLE_NAME_

解决方案也很简单,把

TEMP_TABLE="es_$TABLE_NAME_$V_TS"

改成

TEMP_TABLE="es_${TABLE_NAME}_${V_TS}"

即可,不仅醒目而且消除了歧义!

延伸

扩展一下,上面

V_TS=`date +%Y%m%d`

的写法其实也不太好,用

V_TS=$(date +%Y%m%d)

其实更好。

无独有偶,在计算算数表达式的时候,VAR=$(( 1 + 3 )) 或者 VAR=$[ 1 + 3 ] 就比

VAR=`expr 3 + 1`

来的好。

date 命令小结

最近做的最多的就是把 Hive 查询写到 Shell 脚本中定时调度,常见的有按小时、按天、按周、按月来调度。

所以日期参数的传递就比较重要,这里做一下 date 命令的简单总结。

1. 格式

date [OPTION] [FORMAT]

2. 选项

选项一般是 --date(简写 -d)。

3. 格式

%Y   年,如:2016
%m   月, (01..12)
%d   日,两位
%u   day of week (1..7); 1 is Monday
%V   ISO week number, with Monday as first day of week (01..53)

4. 例子

# 格式化日期
date -d 20160101 +%Y-%m-%d
# 依据某一天进行加减
date -d "-1 days 20160101" +%u
# 文本化的输入
date -d "last sunday" +%Y-%m-%d

SSH 公钥登录与超时断开

最近趁着阿里云针对学生半价买了一个半年的云主机,用下来有两个问题感觉比较蛋疼,一个是每次登录都要输入用户名密码,另一个就是超时自动断开。

公钥登录

ssh 远程连接服务器的验证方法有两种:密码或者公钥。免输密码的解决方法其实很简单,用 expect 命令就可以了。这里介绍一下如何将公钥导入服务器,免除登录时的密码验证。

  1. 前提条件当然是客户端创建了公钥,如果没有请使用 ssh-keygen
  2. 将客户端机器的公钥拷贝至服务器对应用户的家目录下:scp id_rsa.pub user@target_ip:~/
  3. 在服务器上创建 .ssh 目录,并修改权限:mkdir ~/.ssh && chmod 700 ~/.ssh
  4. 创建文件 authorized_keys 并将客户端的公钥写入:cat id_rsa.pub >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys

超时自动断开

阿里云主机大概一分钟左右没有输入客户端就会死在那里。所以需要设置一下 /etc/ssh/sshd_config 来延长超时时间,配置的内容就两条:

ClientAliveInterval 30
ClientAliveCountMax 200

然后重启一下 sshd 服务,反正我是重启了云主机,然后就行了。

以上。

将WordPress安装或者迁移到万网虚拟机上

万网虚拟机

前一阵子在远景论坛上看到万网的虚拟主机免费,果断申请了一个来使用。使用期间也第一次领教了备案带来的麻烦,好在万网的备案服务还是很好很迅速的,幕布的快递也很给力,就是工信部的审核花了十几天时间。从后台的显示来看,万网的虚拟机可以免费使用两年。这个域名note4code.com也是在万网上购买的,49元/年的价格还算公道。

WordPress的安装

万网的虚拟机使用的操作系统是 CentOS,支持 MySQL和 PHP5.3。WordPress 在该虚拟机上的安装比在 Ubuntu 上安装简单多了(参见上一篇博客在 Ubuntu 上安装 WordPress),不需要安装和设置 Apache2、MySQL 和 PHP,不需要为了修改 URL 而进行的大量操作。安装步骤简单概括就是:

  • 下载 WordPress 源代码。
  • 解压缩文件,创建uploads(wp-content/uploads)文件夹。
  • 复制 wp-config-sample.php,重命名为 wp-config.php 并修改其内容。
/** The name of the database for WordPress */
define('DB_NAME', 'database_name_here');

/** MySQL database username */
define('DB_USER', 'username_here');

/** MySQL database password */
define('DB_PASSWORD', 'password_here');

/** MySQL hostname */
define('DB_HOST', 'localhost');

修改为:

/** The name of the database for WordPress */
define('DB_NAME', 'qdmxxxxxxxxx_db');

/** MySQL database username */
define('DB_USER', 'qdmxxxxxxxxx');

/** MySQL database password */
define('DB_PASSWORD', 'xxxxxxxx');

/** MySQL hostname */
define('DB_HOST', 'qdmxxxxxxxxx.my3w.com');
  • 将所有文件复制到 FTP 服务器的 htdocs 目录下。
  • 登录你的域名(审核已通过的情况下),跟随界面操作。

关于WordPress的迁移

可能不少 WordPress 的用户和我一样,在审核期间就现在本地的 WordPress 上写起了博客,打算在审核通过后再将数据迁移到网上,由于网上相关的经验比较少,我也是花了好几天才摸索出了一套方法。

1.导出上传的图片

在写博客的时候不可避免会用到一些图片,这些图片应该都存在我们建立的 uploads 文件夹里,在向 FTP 服务器复制文件的时候记得将原来的 uploads 文件夹合并到 FTP 上去。

2.导出数据库

我们的文章都存在数据库中,所以 WordPress 的迁移本质上是数据库的迁移,我们首先导出数据库:

mysqldump -u wpuser -p wordpress > wordpress.sql

输入该用户的密码后我们得到一个名为 wordpress.sql 的文件,打开文件删除所有类似 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 的语句和注释,仅保留创建表和插入数据的语句。然后使用 Vim(没错,我支持 Vim)的全局替换字符串功能,将 localhost/wordpress 替换为你的域名。

3.导入数据库

进入虚拟主机数据库的管理界面,导入我们修改好的脚本即可。至此大功告成!

注意:在导入数据前最好将本地WordPress使用的插件在虚拟主机上提前装好,避免数据出现错误或者不一致的情况!

在Ubuntu上安装WordPress(附脚本)

WordPress简介

WordPress是一种使用PHP和MySQL开发的博客平台,其实对个人来说也可以当做一个像印象笔记一样的个人知识、内容管理平台。同时各种第三方的插件、主题也让WordPress具备了独特的个性化选择。

手动安装WordPress

安装WordPress相对来说是一个比较简单的过程,只要按照以下步骤执行,一般都可以顺利安装。

1.安装依赖的软件包

第一步要在Ubuntu上安装WordPress所依赖的MySQL、Apache2、PHP等软件包,具体安装方法如下:

sudo apt-get install apache2
sudo apt-get install libapache2-mod-php5 php5
sudo apt-get install mysql-server-5.0  mysql-common mysql-admin
sudo apt-get install php5-mysql

2.配置MySQL

在上一步中安装完MySQL之后记住MySQL的root用户的密码,在这一步中将会使用。使用如下命令来进入MySQL数据库:

mysql -u root -p

然后输入密码进入MySQL。接着我们创建WordPress所使用的数据库:

create database wordpress;

接着为WordPress创建用户并将数据库wordpress的所有权限赋予这个用户:

create user wpuser@localhost identified by 'wppasswd';
grant all privileges on wordpress.* to wpuser@localhost;
flush privileges;

3.下载并安装WordPress

WordPress的安装文件既可以访问中文官方网站进行下载,也可以使用wget命令下载:

wget -c http://wordpress.org/latest.tar.gz

下载完成后解压文件,复制 wp-config-sample.php 文件并重命名为 wp-config.php,并将相关参数进行替换,将

/** The name of the database for WordPress */
define('DB_NAME', 'database_name_here');

/** MySQL database username */
define('DB_USER', 'username_here');

/** MySQL database password */
define('DB_PASSWORD', 'password_here');

/** MySQL hostname */
define('DB_HOST', 'localhost');

修改为:

/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');

/** MySQL database username */
define('DB_USER', 'wpuser');

/** MySQL database password */
define('DB_PASSWORD', 'wppasswd');

/** MySQL hostname */
define('DB_HOST', 'localhost');

其中’DB_HOST’因为MySQL就在本地所以就不用修改localhost,如果数据库位于其他服务器那么就应该填写相应域名或者IP地址。

4.拷贝WordPress解压文件至Apache2服务器目录下

我们要将解压出来的wordpress文件夹拷贝至/var/www/html/下面,为其创建uploads文件夹(保存上传的图片)并修改文件的拥有者为www-data。

sudo rsync -avP ./wordpress /var/www/html/
sudo mkdir /var/www/html/wordpress/wp-content/uploads
sudo chown -R www-data:www-data /var/www/html/wordpress/*

5.安装MySQL扩展

如果遇到“ Your PHP installation appears to be missing the MySQL extension which is required by WordPress ”的情况,一般在 /etc/php5/apache2/php.ini 中将“ extension=/path/to/extension/mysql.so ”改为“ extension=mysql.so ”就可以了。

6.修改apache2的配置文件来允许URL的重写

本步骤的目的是为了允许WordPress能够自定义固定链接的格式。在/etc/apache2/sites-available/000-default.conf中添加下文星号包括的部分:

<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
    *ServerName 127.0.0.1*
    *<Directory /var/www/html/>*
        *AllowOverride All*
    *</Directory>*
    . . .

然后使用如下命令来允许URL的重写以及Apache2的重启:

sudo a2enmod rewrite
sudo service apache2 restart

最后创建 .htaccess 文件并修改其权限与所有者:

touch /var/www/html/wordpress/.htaccess
sudo chown :www-data /var/www/html/wordpress/.htaccess
chmod 664 /var/www/html/wordpress/.htaccess

7.著名的5分钟安装

打开浏览器登录 http://localhost/wordpress 按照图形界面的提示操作即可。

Shell脚本

以下的脚本本人亲测,使用了expect命令来简化设置过程,并添加了几个步骤来修正可能出现的问题.

#!/bin/sh

if [ $# -ne 4 ]; then
    echo "Usage: sudo sh $0 db_name user_name user_passwd mysql_root_passwd"
    exit 1
fi

db_name=$1
user_name=$2
user_passwd=$3
mysql_root_passwd=$4

install_dependency(){
    #1. install the dependency of wordpress
    sudo apt-get install apache2
    sudo apt-get install libapache2-mod-php5 php5
    sudo apt-get install mysql-server mysql-common
    sudo apt-get install php5-mysql
}

config_mysql(){
    #2. configure mysql
sudo apt-get install expect
expect << EOF
set timeout 100
spawn mysql -u root -p
expect {
    "Enter password:" {send "$mysql_root_passwd\r"}
}
expect "mysql>"
send "create database $db_name;\r"
expect "mysql>"
send "create user $user_name@localhost identified by '$user_passwd';\r"
expect "mysql>"
send "grant all privileges on $db_name.* to $user_name@localhost;\r"
expect "mysql>"
send "flush privileges;\r"
expect "mysql>"
send "exit\r"
EOF
}

install_wordpress(){
    #3. install wordpress
    #3.1 download the wordpress
    wget -c http://wordpress.org/latest.tar.gz
    #3.2 extract the files to rebuild wordpress
    tar xvfz latest.tar.gz
    sudo rm -f latest.tar.gz
    #3.3 install some packages to allow you to work with images, install plugins and update portions of your site using ssh.
    sudo apt-get install php5-gd libssh2-php
}

config_wordpress(){
    #4. configure wordpress
    sed "s/database_name_here/$db_name/" ./wordpress/wp-config-sample.php | sed "s/username_here/$user_name/" | sed "s/password_here/$user_passwd/" >> ./wordpress/wp-config.php
}

copy_files(){
    #5. copy files to the document root
    sudo rsync -avP ./wordpress /var/www/html/
    sudo rm -rf ./wordpress
    sudo mkdir /var/www/html/wordpress/wp-content/uploads
    sudo chown -R www-data:www-data /var/www/html/wordpress/*
}

install_mysql_extension(){
    #6. when the problem "Your PHP installation appears to be missing the MySQL extension which is required by WordPress" occurs, you need this.
    sudo sed -i "s/extension=\/path\/to\/extension\/msql.so/extension=mysql.so/" /etc/php5/apache2/php.ini
}

modify_apache_to_allow_url_rewrites(){
    date_time=$(date +%y%m%d%H%M)
    sudo cp /etc/apache2/sites-available/000-default.conf "/etc/apache2/sites-available/000-default.conf.$date_time.bak"
    sudo sed -i "/<\/VirtualHost>/i \        ServerName 127.0.0.1\n        <Directory \/var\/www\/html\/wordpress\/>\n            AllowOverride All\n        <\/Directory>" /etc/apache2/sites-available/000-default.conf
    sudo a2enmod rewrite
    sudo echo 'ServerName localhost' >> /etc/apache2/apache2.conf
    sudo service apache2 restart
}

create_htaccess_file(){
    touch /var/www/html/wordpress/.htaccess
    sudo chown :www-data /var/www/html/wordpress/.htaccess
    chmod 664 /var/www/html/wordpress/.htaccess
}

#install_dependency
config_mysql
install_wordpress
config_wordpress
copy_files
install_mysql_extension
modify_apache_to_allow_url_rewrites
create_htaccess_file

exit 0

参考资料

Vimtutor练习心得

A. 光标定位(482)

ctr + g 显示光标当前所在的行数
shift + g(G) 光标移动到文档末尾
gg 移动到文档首行
数字 + G 移动到指定行

B. 搜索命令(505)

? + 字符串 从当前位置向前查找
/ + 字符串 从当前位置向后查找
n 同向查找上一次的搜索内容
N 反向查找上一次的搜索内容

C. 光标跳转

ctl + o 后退,回到光标跳转之前的位置
ctl + i 前进,跳转到光标的下一个跳转位置

D. 字符串的替换(551)

:[(%|#,#)]s/oldstring/newstring[/(g|c|gc)]
参数说明:

  1. [(%|#,#)]指定替换操作有效行的范围。%表示全文所有行,#,#表示从某一行到另一行之间。不指定该参数表示有效行仅为当前行。
  2. [/(g|gc)]指定替换模式。g表示替换每个有效行内所有符合的字符串,c表示替换有效行内第一个符合的字符串并在替换时提示确认,gc是两者的复合表示替换有效行内所有符合的字符串并在替换时提示确认。不指定该参数表示仅替换每个有效行内遇到的第一个符合的字符串。

E. 文件操作(597)

: + ! + windows命令提示符 调用windows命令
: + r + 空格 + 文件名 将文件入读当前文档
: + w + 空格 + 文件名 将保存当前文件。可以先按“v”选择文本然后保存选定的文件。
: + 行数1 + , + 行数2 + 空格 + w + 空格 + 文件名 将当前文档的特定行保存至文件
: + r + 空格 + ! + windows命令提示符 执行windows命令并将输出信息输入到当前文档

F. 复制粘贴文本(782)

y + 数字 + w 复制若干个单词
y + 数字 + l 复制若干个字母
y + $ 复制到句为
p 粘贴

注(复制多行的方法):
描述:将第 i 行到第 j 行复制到第 k 行之后

(1)
: + i + , + j + 空格 + (co|copy) + k
同理将第 i 行到第 j 行移动到第 k 行之后
: + i + , + j + 空格 + (m|move) + k

(2)
光标移动到结束行,ma
光标移动到起始行,输入y’a
光标移动到需要复制的行,输入p,行前复制则输入大写P

(3)
光标移动到起始行,输入ma
光标移动到结束行,输入mb
光标移动到粘贴行,输入mc
然后输入:’a,’b, co ‘c 把co换成m就是剪切
若要删除多行,则输入:’a,’b de

G. 设置类命令的选项(806)

/字符串\c 仅在本次搜索匹配中忽略大小写
:set ic/ignorecase 搜索时忽略大小写
:set noic/noignorecase 禁用忽略大小写
:set hls/nohlsearch 高亮匹配项highlightsearch
:set nohls/nohlsearch 禁用高亮
:set is/incsearch 使 Vim 在你输入字符串的过程中就显示匹配点
:set nois/noincsearch 关闭输入时匹配

在Ubuntu Linux下安装Code::Blocks和Eclipse CDT

最近小白由于有工作学习的需要,要尝试在Linux下进行C++编程。所以特地花了一点时间研究一下Linux下的C++的IDE。最后我尝试了使用Code::Blocks和Eclipse两个著名的IDE。本文分享一下小白安装过程中遇到的问题和使用心得。

一、Eclipse

1. 安装Eclipse CDT

小白的安装方式是通过以下三条语句来的:

sudo apt-get install eclipse
sudo apt-get install eclipse-pde
sudo apt-get install eclipse-jdt

其实貌似还有一种更加便捷的安装语句。

sudo apt-get install eclipse-cdt eclipse
2. 安装基本编译环境build-essential

安装完eclipse后需要安装基本的编译工具build-essential。安装方法也很简单:

sudo apt-get install build-essential
3. 解决全局菜单不显示的问题

然后我们可以进入eclipse了。进入eclipse后我们会发现全局菜单栏失效!除了Project一栏,其他菜单栏都无法显示!网上查了一下,说是Ubuntu 13.10上Unity的bug。网上给了两种解决方案:

  1. 不要直接启动eclipse,使用env UBUNTU_MENUPROXY= /home/user/eclipse/eclipse启动eclipse,注意等号和eclipse路径之间有个空格。
  2. 建立一个Eclipse的快捷方式,eclipse.desktp内容如下:
Type=Application
Name=Eclipse
Comment=Eclipse Integrated Development Environment
Icon=/usr/share/app-install/icons/eclipse.png
Exec=env UBUNTU_MENUPROXY= eclipse(eclipse的路径)

其实我觉得结合一下两种方法也很简单,直接用root权限去修改现有快捷方式就行了!

sudo vim /usr/share/applications/eclipse.desktop

然后在最后一行加上:

Exec env UBUNTU_MENUPROXY= eclipse

这样在Dash页面搜索eclipse然后启动就能显示全局菜单栏了。

4. 安装CDT插件

添加源:http://download.eclipse.org/tools/cdt/releases/indigo
然后选择第一个CDT Main Feature全部安装

5. 添加环境变量

Preference->C/C++->Build->Environment
Variable:CPP_INCLUDE_PATH
Value:/usr/include/c++/4.8.1(gcc版本)
Variable:C_INCLUDE_PATH
Value:/usr/include

二、Code::Blocks

然后是Code::Blocks,看到一篇帖子推荐说Code::Blocks可以给你如Windows上的Visual Studio一般的便捷。Code::Blocks的安装非常简单,在软件中心里操作即可。

但是在使用Code::Blocks的时候会出现一个问题:某些时候无法输入!求助于万能的互联网,我终于找到了答案:Code::Blocks与ibus输入法不兼容,换成其他输入法即可。

三、使用心得

Eclipse中编辑C++并不像写Java那么方便,至少操作上是的。

一开始,我新建了一个C++文件,在里面输入using std::cout和using std::endl后在main函数中cout依然无法被正确识别。后来我发现一定要保存一次源文件,cout和endl才会被正确识别。

其次Eclipse是可以像提示Java代码一样帮用户补全的,但是一定要用 + </>来激活。我搞不懂这样有意义吗?为啥不能像Java一样方便提示呢?

Code::Blocks给我的使用体验非常好。操作简单、代码提示方便齐全。唯一美中不足的在使用对象或结构体指针取分量的时候不能自动的把“.”替换成“->”。平时C#写惯了,乍一回到C++还真有点不适应。