shell脚本直接执行没有问题,crontab定时执行失败的解决方法

在实行一个shell脚本(调用java程序修改一个excel文件)时,单独sh可以得到想要的结果。

但是放到crontab定时执行后,却没有得到一个新的excel文件,log也没有报错。

查看了路径,文件权限都没有问题,最后在网上查了一下,发现这样可以解决问题:

将crontab执行的语句进行修改:

15 11 * * * /home/loganalysis/dealexcel.sh >> /home/loganalysis/error.log 2>&1

修改为:

15 11 * * * cd /home/loganalysis/ && ./dealexcel.sh >> /home/loganalysis/error.log 2>&1

但是这样可以解决的原因还不是很清楚,希望有大牛可以解释一下。

ImportError: No module named virtualenv报错的解决办法

昨天在执行安装https服务的时候,遇到报错:ImportError: No module named virtualenv

[root@iZ282iltjiwZ https]# ./certbot-auto --nginx certonly
Creating virtual environment...
Traceback (most recent call last):
  File "/usr/bin/virtualenv", line 2, in <module>
    import virtualenv
ImportError: No module named virtualenv

在看到上面的报错之后,我执行安装:

[root@iZ282iltjiwZ https]# yum install virtualenv
Loaded plugins: security
Setting up Install Process
No package virtualenv available.
Error: Nothing to do

发现是包名不对后又更换包名执行安装yum install -y python-virtualenv,发现这些都提示已经安装成功了,如下

[root@iZ282iltjiwZ https]# yum install python-virtualenv
Loaded plugins: security
Setting up Install Process
Package python-virtualenv-12.0.7-1.el6.noarch already installed and latest version
Nothing to do

可是却依然报错No module named virtualenv,原来这是我已经升级过python2.6到2.7版本的原因,yum安装的virtualenv使用的依然还是旧版本的python2.6,而我当前的python用的是2.7,所以会报错,所以解决方法可以是以下两种方法之一

1:更换yum的python版本为2.7再进行安装。

2:从官方下载virtualenv的tar文件包,解压病利用新版本的python安
装:#python2.7 setup.py install。

解决方法如下, 成功安装virtualenv,从而解决了问题。

wget https://files.pythonhosted.org/packages/33/bc/fa0b5347139cd9564f0d44ebd2b147ac97c36b2403943dbee8a25fd74012/virtualenv-16.0.0.tar.gz
sudo tar zxvf virtualenv-16.0.0.tar.gz 
cd virtualenv-16.0.0
python setup.py  install
.....
#成功安装后的提示
Installed /usr/local/python27/lib/python2.7/site-packages/virtualenv-16.0.0-py2.7.egg
Processing dependencies for virtualenv==16.0.0
Finished processing dependencies for virtualenv==16.0.0

virtualenv 指定 python 解释器的版本

使用如下命令为 ubuntu 系统安装 virtualenv

sudo apt-get install python-virtualenv

当我们使用 virtualenv 命令创建虚拟环境时,默认使用的 python 解释器为 /usr/bin/python27(然后创建该解释器对应的副本),那么我们该如何指定解释器的版本呢?

使用参数 -p

$ sudo virtualenv -p ~/anaconda3/bin/python test

sqlalchemy 链接数据库,指定编码,解决密码含有特殊字符

# coding=utf-8
from urllib import quote_plus as urlquote
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData, ForeignKey
import MySQLdb

创建数据库连接

用此方法链接可以指定UTF8编码,同时解决密码中含有特定字符,比如含有@,则把密码部分进行URL编码
echo=True 会显示每条执行的 SQL 语句
max_overflow 最大链接数

connstr = "mysql+mysqldb://root:%[email protected]:3306/dbname?charset=utf8" % urlquote('password')
engine = create_engine(connstr,echo=True,max_overflow=5)

获取元数据

metadata = MetaData()

定义表

user = Table('user', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(20)),
    )

color = Table('color', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(20)),
    )

创建数据表,如果数据表存在,则忽视

metadata.create_all(engine)

创建链接

conn = engine.connect()

增删改查

#插入数据
engine.execute(
    "INSERT INTO color( name) VALUES ('test');"
)
result = engine.execute('select * from color')


print(result.fetchall())

conn.execute(user.insert(),{'name':'test'})

sql = user.insert().values(name='test2')
conn.execute(sql)

#删除一条user表里的 条件是id大于1的
#sql = user.delete().where(user.c.id > 1)
#执行
#conn.execute(sql)

#更新
#把名字为test的修改为aaa
#sql = user.update().where(user.c.name == 'test').values(name='aaa')
#conn.execute(sql)

print "查询user表里的内容"
sql = select([user, ])
res =conn.execute(sql)
print res.fetchall()

print '查询user表下的id'
sql = select([user.c.id, ])
res =conn.execute(sql)
print res.fetchall()

print '查询user表和color表的name,条件是user表的id1=color的id1'
sql = select([user.c.name, color.c.name]).where(user.c.id==color.c.id)
res =conn.execute(sql)
print res.fetchall()

print '查询user表的name,并按照条件排序'
#按照名字排序
sql = select([user.c.name]).order_by(user.c.name)
res =conn.execute(sql)
print res.fetchall()

print '按照id排序'
sql = select([user.c.name]).order_by(user.c.id)
res =conn.execute(sql)
print res.fetchall()

print '查询user表的name,并按照条件分组'
sql = select([user]).group_by(user.c.name)
res =conn.execute(sql)
print res.fetchall()

关闭链接

#关闭链接
conn.close()

tornado 3.0.1 中关于cookie和secure cookie的那点事儿

cookie是附加在http请求中的,tornado默认的set_cookie和get_cookie方法是明文不加密传输的,而set_secure_cookie和get_secure_cookie是明文加密传输的。代码见下:

import tornado.web
import tornado.ioloop

from tornado.options import define,options,parse_command_line
define('port',default=8888,help='run on the port',type=int)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_secure_cookie('username'):
            self.set_secure_cookie('username','littlethunder')
            self.write('just set your cookie')
        else:
            a=self.get_secure_cookie('username')
            print(a)
            self.write('you have cookie before')

def main():
    parse_command_line()
    app=tornado.web.Application(
            [(r'/',MainHandler)],
                cookie_secret='abcde'
            )
    app.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__=='__main__':
    main()

通过浏览器可以看到效果如下:

未分类

如果使用set_secure_cookie和get_secure_cookie方法,那么必须在tornado.web.Application中设置cookie_secret参数,’abcd’只是我测试写的,生产环境下可以用uuid生成一个随机字符串来用,但使用过程中要保证这个字符串恒定。 如果要用set_cookie和get_cookie方法,则只需把上面secure去掉,把cookie_secret参数也去掉即可,cookie就不截图了,是明文的。

最后总结一些cookie和secure_cookie的区别:cookie是基于session的,即打开浏览器设置cookie后,只要不关闭浏览器cookie会一直保存,关闭浏览器后cookie删除,其实就是保存在浏览器运行进程的那块内存中。secure_cookie是保存在硬盘中的cookie,过期时间为一个月,所以一旦设置secure_cookie后,不论浏览器关闭与否,只要不清空cookie,这个值就一直保存直到cookie过期失效。

我们的Tornado项目结构

Tornado项目结构

之前答应过群里几个同学要晒下我们的Tornado项目结构,后来就忘了。。。今天晒出来。

无论是Tornado项目还是Django的项目,大体结构都是一样的。最外层是工程结构,包含了配置、文档、打包等信息,当然还有源码。

项目结构大体都是这样:

project
    - conf/
    - docs/
    - src/
        - package1/
            - __init__.py
            - models.py
        - manager.py
        - server.py
    - setup.py
    - MANIFEST.in

对于项目来说,只考虑两个问题,第一:本地开发是否方便;第二:线上部署是否方便。

开发方便

Django的./manage.py runserver的方式对于本地开发调试就很方便,所以对于Tornado项目来说,也需要有一个类似的机制可以方便的在开发环境中启动项目。

部署方便

因为我们是采用标准的PyPi包分发的方式部署的项目,所有项目文件最终都会落到site-packages中,所以包目录的规划就是个问题。

比如像Django那样,把所有的App作为独立的包分散到site-packages中,还是把源码目录”src”作为独立的包放到site-packages中。

两种不同的方式,在启动时也有所差别,因为包的路径是不一样的。这里不讨论哪种方式更合理,我们只说实际的使用情况。

所以部署方便的点在于,我把包放到site-packages中后是否能方便的启动项目。这意味着包的结构需要兼容本地启动和线上启动。

本地和线上的差别

所以就扯到另外一个问题,本地启动项目时,你当前脚本所在的目录就是默认的包根目录,也就是在sys.path中会加入当前文件所在目录,也就是上面结构中的project/src。你要引用package1下的模块,直接from package1.models import xxxx即可。

而在线上的情况是,源码目录是作为独立包放在site-packages中的。你要引用的话需要from src.package1.models import xxx。

这种本地和线上不同引用的问题在Django中是没有的,除非你调整了Django的结构。

问题解决

包的依赖路径问题,基本上都可以通过sys.path.insert()来解决。
两种解决的方式,一个是改线上的sys.path,一个是改本地的。线上的改动只需要在项目加载时把src目录先insert到sys.path中,作为一个新的根路径。

另外一个就是改本地的启动命令,把src所在的目录加到sys.path中。
从个人的习惯来说,能调整本地逻辑的就不去修改部署环境的逻辑,让其按照默认的方式处理是最稳当的方式。毕竟线上挂了影响很大。

所以我会在本地启动项目是做上面的处理,同时所有包的引用触发是同包的情况下,否则都需要从src开始,也就是:from src.package1.models import xx 。

最终目录结构

project
   ├── MANIFEST.in
   ├── README.md
   ├── conf
   │   └── supervisord.conf
   ├── fabfile
   │   ├── __init__.py
   ├── requirements.txt
   ├── setup.cfg
   ├── setup.py
   └── project_src
       ├── __init__.py
       ├── handlers
       │   ├── __init__.py
       │   ├── base.py
       │   ├── index.py
       ├── server.py
       ├── settings
       │   ├── __init__.py
       │   ├── base.py
       │   └── develop.py
       ├── templates
       │   └── base.html
       ├── url.py
       └── models
           ├── __init__.py
           ├── model1.py
           └── model2.py

其中一server.py的示例代码如下:

#!/usr/bin/env python
import os
import sys
from logging.config import dictConfig
from importlib import import_module
import click
import tornado.ioloop
import tornado.web
from raven.contrib.tornado import AsyncSentryClient
def make_app(debug=None):
    from project_src.url import urls
    assert isinstance(urls, (list, tuple)), 'urls must be list or tuple'
    return tornado.web.Application(urls, debug=debug)
def init_log():
    dictConfig(tornado.settings.LOGGING)
@click.command()
@click.option('--port', default=9090)
@click.option('--profile', default='develop')
def serve(port, profile):
    settings = import_module(f'project_src.settings.{profile}')
    tornado.settings = settings
    init_log()
    app = make_app(settings.DEBUG)
    app.listen(port)
    app.sentry_client = AsyncSentryClient(
        '<sentry>'
    )
    sys.stdout.write(f"Start server at:http://0.0.0.0:{port} nProfile: {profile}n")
    tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
    current_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.insert(0, current_path)
    serve()

最后

其实无论哪种方式,只要能够保证大家保有共识就可以,比如the5fire不去修改线上的加载路径,目的是避免有人去按照常识去修改包目录之后产生不可预知的后果。

flask 更新数据库失败

在使用SQLALchemy更新数据的时候,一直不成功。
数据更新的方式采用ajax,具体代码如下:

$(function () {
    $('#user_profile').click(function (event) {
        event.preventDefault();  //prevent the actual form post
        user_profile();

    });

    // Inject our CSRF token into our AJAX request.
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", $('#csrf_token').val())
            }
        }
    })
})

function user_profile() {
    $.ajax({
        type: $('#tab_1_1 form').attr("method"),
        url: $('#tab_1_1 form').attr("action"),
        data: $('#tab_1_1 form').serialize(),
        contentType: "application/x-www-form-urlencoded",
        success: function (data) {
            console.log(data);
        }
    });
}

但是提交到后台无法获取数据,提交的数据结构如下所示:

csrf_token=IjkwNjQ5OTE2MTJjMGMwZDk4OTc5ZmQxOTZjOTgxMmU4MmU0ZDRmMzAi.DjHgiQ.hDgD-Yz89RkJZPL-YorWJdG7Z7c&fullname=%E9%BB%84%E5%85%B5&nick_name=%E7%8B%AC%E8%A1%8C%E4%BE%A0&phone_number=13429950072&about_me=%E6%88%91%E6%98%AF%E4%B8%80%E4%B8%AA%E5%AE%85%E7%94%B7%EF%BC%8C%E8%AF%B7%E5%A4%A7%E5%AE%B6%E4%B8%8D%E8%A6%81%E6%89%93%E6%89%B0%E6%88%91%E7%BB%A7%E7%BB%AD%E5%AE%85%E3%80%82&website_url=https%3A%2F%2Fpdf-lib.org

之后看一下后台如何接收数据:

# 验证提交
if form.validate_on_submit():
    current_user.fullname = form.fullname.data
    current_user.phone_number = form.phone_number.data
    current_user.nick_name = form.nick_name.data
    current_user.website_url = form.website_url.data
    try:
        sql_query=str(db.session.add(current_user._get_current_object()))
        print(sql_query)
        print(current_user._get_current_object())
        db.session.commit()
    except:
        flash('账户更新失败!')
    flash('账户资料已更新!')

使用这种方式无法获取数据,数据一直没有更新,搞不清是什么情况。之后不断调试,参考了别人文章之后终于把问题解决了。
需要修改源代码如下:

# 验证提交
    if form.validate_on_submit():
        current_user.fullname = request.values.get('fullname', 0)
        current_user.phone_number = request.values.get('phone_number', 0)
        current_user.nick_name = request.values.get('nick_name', 0)
        current_user.about_me = request.values.get('about_me', 0)
        current_user.website_url = request.values.get('website_url', 0)
        try:
            db.session.add(current_user._get_current_object())
            db.session.commit()
        except:
            flash('账户更新失败!')
        flash('账户资料已更新!')

这个是flask获取post参数的方式,具体可以参考这里: https://pdf-lib.org/Home/Details/5201

这样再次获取数据就没有什么问题了。
再次使用SQLALchemy保存数据,数据已经成功更新了。

flask – 生成 secret key

在flask项目中,Session, Cookies以及一些第三方扩展都会用到SECRET_KEY值,这是一个比较重要的配置值。

在使用flask时,我产生了这个错误:the session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret

解决方法是在flask项目开头加入设置SECRET_KEY。

app = Flask(__name__)
app.config['SECRET_KEY'] = '123456'
# or
app.secret_key = '123456'
# or
app.config.update(SECRET_KEY='123456')

如果需要设置一个随机的SECRET_KEY值。我们可以使用os模块的urandom函数来获得随机值。

>>> import os
>>> os.urandom(24)
'xeewxe4xc0xeexb1]x9bxa0x9e)x15Qhemxe5xf17xd6xceBxb7xb4'

linux下使用supervisor监控应用程序

1、应用场景

应用程序需要24小时不间断运行。这时可使用supervisor监控应用程序的进程。当发生应用程序内部错误退出、进程被杀死等情况时,自动重启应用程序。

2、supervisor

supervisor由python写成, 简单好用。官方网站 http://supervisord.org,上面有详细的指南文档。

3、安装supervisor

1)安装 setuptools

$sudo apt-get install python-setuptools

2) 使用easy_install安装 supervisor

$sudo easy_install supervisor

安装完成后出现:

/usr/bin/supervisord --supervisor服务守护进程
/usr/bin/supervisorctl--supervisor服务控制程序

4、配置文件

以下命令可以将配置文件示例打印到控制台:

$ echo_supervisord_conf

若要生成配置文件,使用命令:

$ sudo echo_supervisord_conf > /etc/supervisord.conf

或者在当前目录生成:

$ echo_supervisord_conf > supervisord.conf

Notice: 如何停止子进程

场景:如果supervisord.conf中配置的command是执行一个bash,而bash里执行java,那么当使用supervisorctl stop [programname]停止程序时,只有上层进程被停止,而java进程没有被停止。

解决办法:

在配置文件中设置:

stopasgroup=true               
killasgroup=true 

5、配置要运行的程序

在上面生成的配置文件后,追加:

[program:helloworld]
command=./helloworld              ;执行命令
process_name=%(program_name)s
autostart=true                   ; 程序是否随supervisor启动而启动
autorestart=true                 ;程序停止时,是否自动重启
startsecs=10

6、启动supervisor

$ supervisord -c supervisord.conf

7、使用supervisorctl管理程序

1)开启/停止某个程序

# supervisorctl [start | stop] [program名称]      //在supervisord.conf中定义的

2)查看进程状态

$supervisorctl status

8、通过web方式查看状态

配置文件中配置:

[inet_http_server]         ; inet (TCP) server disabled by default
port=127.0.0.1:9001        ; (ip_address:port specifier, *:port for all iface)

那么可以从浏览器通过访问 localhost:9001来查看进程状态

9、添加supervisord为Linux系统服务,开机自动启动

1)将supervisord.conf拷贝到 /etc 目录下

2)启动脚本 supervisord.sh (centos/redhat)

#!/bin/sh
#
# /etc/rc.d/init.d/supervisord
#
# Supervisor is a client/server system that
# allows its users to monitor and control a
# number of processes on UNIX-like operating
# systems.
#
# chkconfig: - 64 36
# description: Supervisor Server
# processname: supervisord

# Source init functions
. /etc/rc.d/init.d/functions

prog="supervisord"

prog_bin="/usr/local/bin/supervisord"
PIDFILE="/tmp/supervisord.pid"

start()
{
        echo -n $"Starting $prog: "

# Source init functions
. /etc/rc.d/init.d/functions

prog="supervisord"

prog_bin="/usr/local/bin/supervisord"
PIDFILE="/tmp/supervisord.pid"start()
{
        echo -n $"Starting $prog: "
        daemon $prog_bin --pidfile $PIDFILE
        [ -f $PIDFILE ] && success $"$prog startup" || failure $"$prog startup"
        echo
}

stop()
{
        echo -n $"Shutting down $prog: "
        [ -f $PIDFILE ] && killproc $prog || success $"$prog shutdown"
        echo
}

case "$1" in
  start)
    start
  ;;
  stop)
    stop
  ;;
  status)
        status $prog
  ;;
  restart)
    stop
    start
  ;;
  *)
    echo "Usage: $0 {start|stop|restart|status}"
  ;;

esac

3)添加为系统服务

# mv supervisord.sh  /etc/init.d/supervisord
# chkconfig --add  supervisord
# chkconfig --level 345 supervisord on

4) 开启/停止服务

# service supervisord [start | stop]