-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 54.6 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 54.6 KB
1
[{"title":"Flask入门小结","date":"2017-04-19T16:00:00.000Z","path":"2017/04/20/Flask入门小结/","text":"这篇笔记是对于使用Flask开发网站过程的一些总结,仅仅是一些很浅显的部分,不涉及具体实现,而是试图提炼出一些常用的,项目设计层面的东西。 组织项目我习惯使用Python包的形式来进行项目文件管理,因为单个文件只适合用来开发很小的练手项目(几百行代码搞定),而现实中往往需要上万行的代码来开发一个中型的项目,更别说是大型网站项目了。 一般来说,项目包的结构是这样的: 123456789101112131415Imitation-weibo/ config.py # 配置文件 requirements.txt # 应用依赖的所有Python包 manage.py # 启动文件 instance/ # 存储不能公开的配置,比如SECRET_KEY等 config.py app/ # 应用所在的包 __init__.py views.py # 路由 forms.py # 表单 models.py # 应用模型 static/ # 静态文件,包括图标,css等文件 *.icon etc templates/ # 模板文件 *.html 配置简单的配置先随意写一个配置文件config.py:12DEBUG = TrueSECRET_KEY = 'NI CAI A' 可以在应用中导入配置并访问: 12345from flask import Flaskapp = Flask(__name__)# 导入配置app.config.from_object('config')# 在这之后就可以通过app.config['DEBUG']访问相应的变量了 重要的配置变量有几个配置变量比较重要,基本上是所有的web应用都需要使用的。 DEBUG,在调试错误时能够给出信息的工具。在开发与环境下应该设置为True;在生产环境下必须设置为False,否则任意用户都能在你的服务器上运行Python代码。 SECRET_KEY,Flask用这个密钥对cookies以及其他变量进行加密。 如果你不想你的配置变量被他人知道,那么最好将这些变量定义放置在instance文件夹中,并保持在版本控制之外。如果想要加载instance文件夹中的配置变量,可以使用如下方式: 12345# 首先要设置instance_relative_config为Trueapp = Flask(__name__, instance_relative_config=True)app.config.from_object('config')# 然后用以下语句查看在instance文件夹中的特殊文件app.config.from_pyfile('config.py') 依照环境变量来配置在应用有多个配置共存的情况下,可以把所有的文件都放置到config包之下,如下所示: 12345config/ __init__.py default.py production.py development.py 然后通过如下方式导入配置: 12345app = Flask(__name__, instance_relative_config=True)app.config.from_object('config.default')app.config.from_pyfile('config.py')# 加载由环境变量APP_CONFIG_FILE指定的文件app.config.from_envvar('APP_CONFIG_FILE') 视图和路由缓存我们可以使用flask-cache扩展实现缓存,它提供了一个可以用来缓存某个响应一段时间的装饰器。也可以将flask-cache配置成和后台缓存一起使用,强烈推荐Redis。假设flask-cache已经配置好,使用方式如下: 12345678910111213from flask_cache import Cachefrom flask import Flask, render_templateapp = Flask()@app.route('/')@cache.cached(timeout=60)def index(): # ... return render_template( 'index.html', lastest_posts = lastest_posts, recent_user = recent_user recent_photos = rencent_photos ) 现在这个函数将会在每60秒最多运行一次。响应的结果会被保存在缓存中,并可以让期间的每一个请求获取。 自定义装饰器自定义装饰器分为带参数和不带参数两种: 1234567# 不带参数def decorator(func): @wraps(func) def decorator_function(*args, **kwargs): # ... return func(*args, **kwargs) return decorator_function 当用@decorator装饰一个函数时,decorator()被调用,被装饰的函数作为一个参数被传递进来。 @wraps是一个装饰器,告诉Python解释器函数decorator_function()包装了视图函数func()。 decorator_function()将截取原本传递给视图函数func()的参数,然后做一些其他的处理。 最后decorator_function()将原来的参数传递给视图函数func()进行处理。 带参数的装饰器1234567def decorator(param): def decorator_func(func): @wraps(func) def decorator_function(*args, **kwargs): return func(*args, **kwargs) return decorator_function return decorator_func 使用的时候可以用@decorator(param),param表示你需要传入的参数。 蓝本蓝本定义了可用于单个应用的视图、模板、静态文件等等的集合,它允许你将不同的路由分开,提供一些规范,同时也可以要求蓝本针对不同路由应用不同的静态文件,导致不同的URL出现不一样的网站界面。 Flask中,蓝本有以下用途: 把一个应用分解为一套蓝本。这是针对大型应用的理想方案:一个项目可以实例化一个 应用,初始化多个扩展,并注册许多蓝本。 在一个应用的 URL 前缀和(或)子域上注册一个蓝本。 URL 前缀和(或)子域的参数 成为蓝本中所有视图的通用视图参数(缺省情况下)。 使用不同的 URL 规则在应用中多次注册蓝本。 通过蓝本提供模板过滤器、静态文件、模板和其他工具。蓝本不必执行应用或视图 函数。 当初始化一个 Flask 扩展时,为以上任意一种用途注册一个蓝本。 Flask中的蓝本并不是一个可插拔的应用,因为它不是一个真正的应用,而是一套可以注册在应用中的操作,并且可以注册多次。那么为什么不使用多个应用对象呢?可以使用多个应用对象,但是这样会导致每个应用都有自己独立的配置,且只能在WSGI层中进行应用管理。 如果使用蓝本,那么应用可以在Flask层中进行管理,共享配置,通过注册按需改变应用对象。蓝本的缺点是一旦应用被创建后,只有销毁整个应用对象才能注销蓝本。 功能式架构在功能式架构中,按照每部分代码的功能来组织应用,所有的模板放到同一个文件夹中,静态文件放在另一个文件夹中,而视图放在第三个文件夹中。 12345678910app/ __init__.py static/ ... templates/ ... views.py/ auth.py main.py .... 在views文件夹下的每一个.py文件都是一个蓝本,需要在Flask对象中注册后才能使用。使用功能式架构,创建蓝本时候非常方便,模板和静态文件文件夹是固定的: 12from flask import Blueprintmain = Blueprint('main', __name__) 分区式架构在分区式架构中,按照每一部分所属的蓝本来组织应用。 123456789101112app/ __init__.py auth/ __init__.py views.py static/ templates/ main/ __init__.py views.py static/ templates/ 每一个app/之下的文件夹都是一个独立的蓝本,所有的蓝本都通过顶层init.py注册到Flask对象中。 使用分区式架构,创建蓝本时候需要指明模板和静态文件文件夹: 123456from flask import Blueprintmain = Blueprint( 'main', __name__, template_folder='templates', static_folder='static') 如果你的网站应用有很多页面差别很大,可重用的静态文件和模板非常少,那么建议采用分区式架构;如果你的网站应用的组件之间联系较为紧密,可以共用很多的静态文件和模板,使用功能式架构会更好。 使用URL前缀蓝本允许我们定义静态的或动态的前缀。举个栗子,我们告诉Flask所有蓝本中的路由应该以/auth作为前缀,这样是一个静态前缀;也可以传入参数,在URL中对应片段中输入的文本将决定我们输出的视图,这样是一个动态前缀。 1234# 静态前缀,其URL为:http://..../authapp.register_blueprint(auth, url_prefix='/auth')# 动态前缀,其URL为:http://..../auth/<username>,<username>取决于输入app.register_blueprint(auth, url_prefix='/auth/<username>') 模板对于模板文件夹,其放置的最佳位置是app/templates,直接放置在包文件夹下,模板的结构应当平行于对应的路由的结构。 模板继承为了提高模板的可重用性,最好定义几个适用于所有子模板的主题结构的基础摸板。然后通过extends和block标签实现继承。在子模板中,可以拓展父模板并定义block里面的内容,这和Python类的继承基本一致。并且,在模板中也有super()函数,可以在子模板中加载父模板中这个block的内容。 宏模板除了使用基础模板外,还可以将反复出现的代码片段抽象成宏,它提供了模块化模板代码的一种方式。 来看一下具体实现: 123456789{% from "macros.html" import nav_link with context %}{% block title %}Title{% endblock %}<body> <ul class="nav-list"> {{ nav-link("home", "Home") }} {{ nav-link("about", "About") }} {{ nav-link("contact", "Contact") }} </ul></body> 这个文件里调用了尚未定义的宏(nav-link),并传递两个参数给它,接下来我们在macros.html中定义宏: 123{% macro nav_link(endpoint, text) %}<!-- 一些要处理的内容 -->{% endmacro %} 注意:我们在import语句中加入了with_context。jinja上下文包括了通过render_template()函数传递的参数以及在我们Python代码的jinja环境上下文,这些变量能够用于模板的渲染。 静态文件静态文件是那些不会改变的文件,一般情况下,这包括CSS文件,JavaScript文件和一些图片、图标文件等等。我们一般都会把静态文件放置在static/文件夹下面,并且可以把来自于扩展的静态文件归类到static/lib/目录下,方便管理。 使用flask-assets扩展来管理静态文件flask-assets是一个管理静态文件的插件,提供了两种非常有用的特性:允许Python代码中定义多组可以同时插入模板的静态文件。允许预处理这些静态文件。假设你现在的static文件夹结构是这样的: 123456789101112131415static/ css/ lib/ reset.css common.css auth.css main.css js/ lib/ reset.js common.js auth.js main.js img/ favicon.ico 我们的应用有auth和main两个部分,每个部分有一个JavaScript和CSS分组。将所有的静态文件放入util包中的assets模块。 1234567891011121314151617181920212223# app/util/assets.pyfrom flask_assets import Bundle, Environmentfrom .. import appbundles = { 'auth_js': Bundle( 'js/lib/reset.js', 'js/auth.js', output='gen/auth.js'), 'auth_css': Bundle( 'js/lib/reset.cwss', 'js/auth.css', output='gen/auth.css'), 'main_js': Bundle( 'js/lib/reset.js', 'js/main.js', output='gen/main.js'), 'main_css': Bundle( 'js/lib/reset.css', 'js/main.css', output='gen/main.css'), }assets = Environment(app)assets.register(bundles) flask-asset会自动按照被列出来的顺序合并静态文件,并且在最后把分组注册到util.assets中,使得我们可以在init.py中导入: 1from .util import assets 然后我们就可以在模板中使用静态文件了: 123456{% assets 'auth.js' %}<!-- 填写需要的内容 -->{% endassets %}{% assets 'auth.css' %}<!-- 填写需要的内容 -->{% endassets %} 数据库SQLAlchemy是一个ORM,基于对目标数据库的原生SQL抽象,它提供了与很多数据库引擎一致的API。在Flask应用中,我们大多数时候使用的是flask-sqlalchemy这个扩展来管理数据库。 首先,你要创建并初始化数据库对象: 12345678from flask_sqlalchemy import SQLAlchemyfrom flask import Flaskapp = Flask(__name__, instance_relative_config=True)# 配置app.config.from_object('config')app.config.from_pyfile('config.py')# 创建并初始化数据库对象db.SQLAlchemy(app) 然后你就可以定义数据库模型,并通过命令db.create_all()创建数据库模型中的表。数据库模型并非一直不变的,很多时候都需要进行修改,Alembic是专用于SQLAlchemy的数据库迁移工具。它允许你保持你的数据库模式的版本历史,这样你就可以升级到一个新的模式,或者降级到旧的模式. 通过初始化的almbic init命令,可以创建一个迁移环境,自动生成的alembic/文件夹中包括了在版本间迁移数据的脚本。同时会有一个包括配置信息的alembic.ini文件。一旦我们的迁移脚本已经准备就绪,我们只需运行alembic upgrade head来迁移我们的数据到最新版本。 表单表单是允许用户与web应用交互的基本元素,Flask通过flask-wtf扩展应用WTForms包来处理表单。 首先,你要定义一个表单类: 123456from flask_wtf import Formfrom wtforms import StringField, PasswordFieldfrom wtforms.validators import DataRequired, Emailclass EmailPasswordForm(Form): email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()]) 然后可以在路由中验证表单: 123456import EmailPasswordForm@auth.route('/login', methods=['GET', 'POST'])def login(): form = EmailPasswordForm() if form.validate_on_submit(): # ... 在模板中渲染表单: 1234<body> {{ form.email }} {{ form.password }}</body> flask-wtf提供CSRF防护的功能,我们在模板中使用CSRF防护: 123<body> {{ form.csrf_token }}</body> 密码安全在Web应用中,不应该明文保存密码,而是要用Bcrypt算法hash密码。flask-bcrypt扩展提供了应用bcrypt包的功能。 12345678from flask_bcrypt import Bcrypt, generate_password_hash# 创建并初始化扩展bcrypt = Bcrypt(app)# 生成密码hash,第二个参数为round次数# round值越大,说明加密的层数越多,加密越耗时间,越难破解generate_password_hash('password', 12)# 验证密码bcrypt.check_password_hash(password_hash, password)","tags":[{"name":"pyhton","slug":"pyhton","permalink":"https://dangod.github.io/tags/pyhton/"},{"name":"web开发","slug":"web开发","permalink":"https://dangod.github.io/tags/web开发/"},{"name":"flask","slug":"flask","permalink":"https://dangod.github.io/tags/flask/"}]},{"title":"Flask-Login的使用","date":"2017-04-19T16:00:00.000Z","path":"2017/04/20/Flask-Login的使用/","text":"我们使用Flask-Login来实现用户登陆功能,这篇笔记主要介绍Flask-Login扩展的使用,官方文档:Flask-Login 1. 初始化Flask-Login对一个使用Flask-Login的应用最重要的一部分就是LoginManager类,你需要创建它,并对它进行初始化用来实现登陆。 12345from flask.ext.login import LoginManager#创建login_manager = LoginManager()#初始化login_manager.init_app(app) 2. 回调函数要使用Flask-Login扩展,你必须在数据库模型文件(models.py)中提供一个回调函数user_loader,这个回调函数用于从会话中存储的用户ID重新加载用户对象。它应该接受一个用户的id作为输入,返回相应的用户对象。 123@login_manager.user_loaderdef load_user(user_id): return User.get(user_id) 如果ID无效的话,它会返回None,而不是抛出异常。 3. 用户类要使用Flask-Login函数,你用来表示用户的类必须实现以下方法: is_authenticated is_active is_anonymous get_id() 如果你不想手动定义这些方法,Flask-Login提供了UserMixin类,你只要继承这个类就行,它提供了对这些方法的默认实现。 123from flask.ext.login import UserMixinclass User(UserMixin, db.Model): .... 4. 用户登陆登出4.1 用户登陆4.1.1 login_user函数通过验证之后,你可以使用login_user函数让用户登陆。next参数的作用是,当你未登录时,访问一个需要登陆才能访问的URL,网站会先跳转到登陆页面让你登陆,而你之前想访问的URL则被保存在next参数内,这个参数可从request.args字典中读取,在你登陆之后会自动跳转到你之前想要访问的URL而不是首页。必须在程序中验证next参数的值,如果不验证的话,你的应用将会受到重定向的攻击。 1234567891011@app.route('/login', methods=['GET', 'POST'])def login(): form=LoginForm() if form.validate_on_submit(): ... login_user(user) next = flask.request.args.get('next') if not next_is_valid(next): return flask.abort(400) return flask.redirect(next or flask.url_for('index')) return flask.render_template('login.html', form=form) 4.1.2 @login_required装饰器如果你想设置某个视图函数是需要登陆才能访问的,可以使用@login_required装饰器。 1234@app.route('/index')@login_requireddef index(): .... 这样,你就需要登陆要访问index页面。 4.1.3 定制登陆过程如果未登录的用户尝试访问一个login_required装饰的视图函数,Flask-Login会闪现一条消息并且重定向到登录视图提醒你登陆。(如果未设置登录视图,它将会以401 错误退出)。 登陆视图的名称可以用Login_Manager.login_view来设置。例如: 1login_manager.login_view = 'auth.login' 默认闪现的消息是Please log in to access this page.。要自定义该消息,可以设置LoginManager.login_message。例如: 1login_manager.login_message = '请登录后访问' 要自定义消息分类的话,可以设置LoginManager.login_message_category。例如: 1login_manager.login_message_category = 'info' 还可以使用Request Loader定制登陆,详情请参阅官方文档。 4.2 用户登出用户登出就简单多了,只需要调用logout_user函数即可,用户会被登出,且会话产生的cookie会被清理干净。 12345@app.route('/logout')@login_requireddef logout(): logout_user() return flask.redirect('index') 5. 匿名用户默认情况下,当一个用户没有真正的登陆,current_user被设置成一个AnonymousUserMixin对象。它有如下的属性和方法: is_active和is_authenticated的值为False is_anonymous的值为True get_id()返回None如果需要为匿名用户定制一些需求,你可以向LoginManager提供一个创建匿名用户的回调: 1login_manager.anonymous_user = MyAnonymousUser 6. 记住我只要把remember传递给login_user,Flask-Login会自动处理。但你也可以提供额外的设置来增强你记住的cookie的安全性。详情请参阅官方文档。 7. 会话保护当上述特性保护“记住我”令牌免遭cookie窃取时,会话cookie仍然是脆弱的。Flask-Login 包含了会话保护来帮助阻止用户会话被盗用。你可以在LoginManager上和应用配置中配置会话保护。如果它被启用,它可以在basic或strong两种模式中运行。要在LoginManager上设置它,设置session_protection属性为“basic” 或 “strong”或”None”(禁用): 1login_manager.session_protection = 'strong'","tags":[{"name":"pyhton","slug":"pyhton","permalink":"https://dangod.github.io/tags/pyhton/"},{"name":"web开发","slug":"web开发","permalink":"https://dangod.github.io/tags/web开发/"},{"name":"flask","slug":"flask","permalink":"https://dangod.github.io/tags/flask/"}]},{"title":"在Flask中操作数据库","date":"2017-04-19T16:00:00.000Z","path":"2017/04/20/在Flask中操作数据库/","text":"前言Web程序最常用基于关系模型的数据库,这种数据库也称为SQL数据库,因为它们使用结构化查询语言。关系型数据库把数据存储在表中,表模拟程序中的不同实体。简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。关系模型中常用的概念: 关系:可以理解为一张二维表,每个关系都具有一个关系名,就是通常说的表名。 元组:可以理解为二维表中的一行,在数据库中经常被称为记录。 属性:可以理解为二维表中的一列,在数据库中经常被称为字段。 域:属性的取值范围,也就是数据库中某一列的取值限制。 关键字:一组可以唯一标识元组的属性,数据库中常称为主键,由一个或多个列组成。关系模式:指对关系的描述。其格式为:关系名(属性1,属性2, … … ,属性N),在数据库中称为表结构。 Python数据库框架由于我们使用的是Flask框架来搭建我们的Web应用,因此选用集成了Flask的数据库框架对我们来说是最合适,也是最节省时间的。因此,选择Flask-SQLAlchemy扩展来实现,这个扩展包装了SQLAlchemy框架。 Flask-SQLAlchemy框架的配置下面是Flask-SQLAlchemy中常用的配置值。Flask-SQLAlchemy从Flask主配置中加载这些值。注意其中的一些在引擎创建后不能修改,所以确保尽早配置且不在运行时修改它们。 SQLALCHEMY_DATABASE_URI:用于数据库的连接,例如sqlite:////tmp/test.db SQLALCHEMY_TRACK_MODIFICATIONS:如果设置成True(默认情况),Flask-SQLAlchemy将会追踪对象的修改并且发送信号。这需要额外的内存,如果不必要的可以禁用它。 SQLALCHEMY_COMMIT_ON_TEARDOWN:每次request自动提交db.session.commit() 在这里我们只列出了在Imatation-weibo程序中需要使用的配置键,更多的配置键请参考Flask-SQLAlchemy官方文档。 常见数据库的连接URI格式如下所示: Postgres:postgresql://scott:tiger@localhost/mydatabase MySQL:mysql://scott:tiger@localhost/mydatabase Oracle:oracle://scott:tiger@127.0.0.1:1521/sidname SQLite:sqlite:////absolute/path/to/foo.db Flask-SQLAlchemy框架的初始化常见情况下,对于只有一个Flask应用,我们需要先创建Flask应用,选择加载配置,然后创建SQLAlchemy对象时候把Flask应用传递给它作为参数。一旦创建,这个对象就包含 sqlalchemy 和 sqlalchemy.orm 中的所有函数和助手。此外它还提供一个名为 Model 的类,用于作为声明模型时的 delarative 基类。 创建并初始化Flask-SQLAlchemy,当然由于我们的程序使用了工厂函数,因此,我们在这里也用之前初始化Flask-Bootstrap扩展一样的方法来实现它: 123456from flask.ext.sqlalchemy import SQLAlchemydb = SQLAlchemydef create_app(config_name): #省略号部分包含了创建app等代码,请查看前面的章节 ... db.init_app(app) 声明模型创建数据库模型创建数据库模型的方法如下,创建表时必须导入基类: 12345class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) def __repr__(self): return '<User %r>' % self.username 这个模型创建了两个字段,他们是类db.Column的实例,id和username,db.Column 类构造函数的第一个参数是数据库列和模型属性的类型,下面列出了一些常见的列类型以及在模型中使用的Python类型。 Integer:普通整数,一般是32bit Text:变长字符串,对较长或不限长度的字符做了优化 Text:变长字符串,对较长或不限长度的字符做了优化 Boolean:布尔值 Date:日期 DateTime:日期和时间 db.Column 中其余的参数指定属性的配置选项。下面列出了一些常用选项: primary_key:如果设置为True,这列就是表的主键 unique:如果设置为True,这列不允许出现重复的值 index:如果设置为True,为这列创建索引,提升查询效率 default:为这列定义默认值 一对多关系最为常见的关系就是一对多关系,因为关系在它们建立之前就已经声明。关系使用relationship()函数表示,外键使用类sqlalchemy.schema.ForeignKey来单独声明。 在一对多关系中,要在”多”这一侧加入一个外键,指向”一”这一侧联接的记录,即relationship()声明出现在代表”少”那个类,而外键声明出现在代表”多”的那个类中。如下代码所示,每一个人(少)可以有多个地址(多): 123456class Person(db.Model): id = db.Column(db.Integer, primary_key=True) address = db.relationship('Address', backref='person', lazy='dynamic')class Address(db.Model): id = db.Column(db.Integer, primary_key=True) person_id = db.Column(db.Integer, db.ForeignKey('person.id')) 关系使用address表中的外键连接了两行。添加到address模型中person_id列被定义为外键,就是这个外键建立起了联系。传给db.ForeignKey()的参数’person_id’表明,这一列的值是person表中行的id值。 添加到person表中的address属性代表这个关系的面向对象视角。对于一个person实例,其address属性将返回与person相关联的多个地址。db.relationship()的第一个参数表明这个关系的另一端是哪个模型。 db.relationship()中的backref参数向address模型中添加一个person属性,从而定义反向关系。这一属性可替代person_id访问 person模型,此时获取的是模型对象,而不是外键的值。 大多数情况下,db.relationship()都能自行找到关系中的外键,但有时却无法决定把哪一列作为外键。例如如果address模型中有两个或以上的列定义为person模型的外键,SQLAlchemy就不知道该使用哪列。如果无法决定外键,你就要为db.relationship()提供额外参数,从而确定所用外键,常用的配置选项如下所示: backref:在关系的另一个模型中添加反向引用 primaryjoin:明确指定两个模型之间使用的联结条件。只在模棱两可的关系中需要指定 lazy:决定了SQLAlchemy什么时候从数据库中加载数据。可选值有 select(首次访问时按需加载)、immediate(源对象加 载后就加载)、 joined(加载记录,但使用联结)、 subquery (立即加载,但使用子查询), noload(永不加载)和 dynamic(不加载记录,但提供加载记录的查询) uselist:如果设为Fales,表示一对一关系 order_by:指定关系中记录的排序方式 secondary:指定多对多关系中关系表的名字 secondaryjoin:SQLAlchemy无法自行决定时,指定多对多关系中的二级联结条件如果想为反向引用(backref)定义惰性(lazy)状态,可以使用backref()函数: 12345class Person(db.Model): id = db.Column(db.Integer, primary_key=True) address = db.relationship('Address', backref=db.badkref('person', lazy='joined'), lazy='dynamic') 多对多关系一对多关系,一对一关系至少有一侧是单个实体,所以记录之间的联系可以通过外键来实现,让外键指向这个实体。但是两侧都是多的关系,显然不能通过一个简单的外键来实现。解决办法是添加第三张表。 多对多关系一个典型的例子是文章与标签之间的关系,一篇文章可以有多个标签,一个标签也可以对应多篇文章。 我们把tags和posts表之间的多对多关系转换成它们各自与关联表connections之间的两个一对多关系。 查询这个多对多关系分为两步。若想知道某篇文章有多少个标签,首先从posts和connections之间的一对多关系开始,获取这篇文章在connections表中的所有和这篇文章相关的记录,然后再按照多到一的关系在tags表中查找对应的所有标签。 同样,若想查找某个标签所对应的所有文章,首先从tags表和connections表之间的一对多关系开始,获取这个标签在connections表中所有的和这个标签相关的记录,然后再按照多到一的关系在posts表中查找对应的所有文章。 123456789101112connections = db.Table('connections', db.Column('posts_id', db.Integer, db.ForeignKey('posts_id')), db.Column('tags_id', db.Integer, db.ForeignKey('tags_id')))class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True) tags = db.relationship('Tag', secondary=connections, backref=db.backref('posts', lazy='dynamic'), lazy='dynamic')class Tag(db.Model): __tablename__ = 'tags' id = db.Column(db.Integer, primary_key=True) 多对多关系仍使用定义一对多关系的db.relationship()方法进行定义,但在多对多关系中,必须把secondary参数设为关联表。多对多关系可以在任何一个类中定义,backref参数会处理好关系的另一侧。关联表connections就是一个简单的表,不是模型,SQLAlchemy会自动接管这个表。 自引用关系多对多关系在我们的Web应用中可以用来实现用户之间的关注,但是在上面的文章和标签的例子中,关联表连接的是两个明确的实体,而在用户关注其他用户时,都在users表内,只有一个实体。如果关系中的两侧都在同一个表中,这种关系称为自引用关系。在关注中,关系的左侧是用户实体,称为”关注者”;右侧也是用户实体,称为”被关注者”。这种用户之间关注的关系,我们依然可以使用上面的方法来实现。 高级多对多关系自引用多对多关系可在数据库中表示用户之间的关注,但却有个限制。使用多对多关系时,往往需要存储所联两个实体之间的额外信息。对用户之间的关注来说,可以存储用户关注另一个用户的日期,这样就能按照时间顺序列出所有关注者。这种信息只能存储在关联表中,但是在之前实现的学生和课程之间的关系中,关联表完全是由SQLAlchemy掌控的内部表。 为了能在关系中处理自定义的数据,我们必须提升关联表的地位,使其变成程序可访问的模型。 12345class Follow(db.Model): __tablename__ = 'follows' follower_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True) followed_id = db.Column(db.Integer, db.Foreignkey('users.id'), primary_key=True) timestamp = db.Column(db.DateTime, default=datetime.utcnow) SQLAlchemy不能直接使用这个关联表,因为如果这么做程序就无法访问其中的自定义字段。相反地,要把这个多对多关系的左右两侧拆分成两个基本的一对多关系,而且要定义成标准的关系。 123456789class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) followed = db.relationship('Follow', foreign_keys=[Follow.follower_id], backref=db.backref('follower', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') follower = db.relationship('Follow', foreign_keys=[Follow.followed_id], backref=db.backref('followed', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') followd和follower都定义为单独的一对多关系,为了消除外键之间的歧义,定义关系时必须选用可选参数foreign_keys指定外键。而且,db.backref()不指定这两个关系之间的引用关系,而是回引Follow模型。 回引中的 lazy 参数指定为joined。这个lazy模式可以实现立即从联结查询中加载相关对象。例如,如果某个用户关注了100个用户,调用user.followed.all()后会返回一个列表,其中包含100个Follow实例,每一个实例的follower和followed回引属性都指向相应的用户。设定为lazy=’joined’模式,就可在一次数据库查询中完成这些操作。如果把lazy设为默认值select,那么首次访问follower和followed属性时才会加载对应的用户,而且每个属性都需要一个单独的查询,这就意味着获取全部被关注用户时需要增加100次额外的数据库查询。 这两个关系中,User一侧设定的lazy参数作用不一样。lazy参数都在“一”这一侧设定,返回的结果是“多”这一侧中的记录。上述代码使用的是dynamic,因此关系属性不会直接返回记录,而是返回查询对象,所以在执行查询之前还可以添加额外的过滤器。 cascade 参数配置在父对象上执行的操作对相关对象的影响。比如,层叠选项可设定为:将用户添加到数据库会话后,要自动把所有关系的对象都添加到会话中。层叠选项的默认值能满足大多数情况的需求,但对这个多对多关系来说却不合用。删除对象时,默认的层叠行为是把对象联接的所有相关对象的外键设为空值。但在关联表中,删除记录后正确的行为应该是把指向该记录的实体也删除,因为这样能有效销毁联接。这就是层叠选项值delete-orphan的作用。 操作数据库我们可以在shell模式下进行这些操作。 注意:需要完成Chapter5的内容才能来操作这些命令,因为需要在manage.py文件中加入数据库的代码才能在shell中运行db实例。 1python manage.py shell 123456789101112131415#创建表db.create_all()#删除表db.drop_all()#插入行user_john=User(username='john')#添加到数据库会话db.session.add(user_john)#提交db.session.commit()#删除行db.session.delete(user_john)db.session.commit()#查询行User.query.all() 使用过滤器可以配置query对象进行更精确的数据库查询。下面列出常用的过滤器,完整的列表请参见SQLAlchemy官方文档: filter():把过滤器添加到原查询上,返回一个新查询 filter_by():把等值过滤器添加到原查询上,返回一个新查询 limit():使用指定的值限制原查询返回的结果数量,返回一个新查询 offset():偏移原查询返回的结果,返回一个新查询 order_by():根据指定条件对原查询结果进行排序,返回一个新查询 group_by():根据指定条件对原查询结果进行分组,返回一个新查询 在查询上应用指定的过滤器后,通过调用all()执行查询,以列表的形式返回结果。除了all()之外,还有其他方法能触发查询执行。下面列出常用的执行查询方法: all():以列表形式返回查询的所有结果 first():返回查询的第一个结果,如果没有结果,则返回 None first_or_404():返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误响应 get():返回指定主键对应的行,如果没有对应的行,则返回 None get_or_404():返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回 404 错误响应 count():返回查询结果的数量 paginate():返回一个 Paginate 对象,它包含指定范围内的结果","tags":[{"name":"pyhton","slug":"pyhton","permalink":"https://dangod.github.io/tags/pyhton/"},{"name":"web开发","slug":"web开发","permalink":"https://dangod.github.io/tags/web开发/"},{"name":"flask","slug":"flask","permalink":"https://dangod.github.io/tags/flask/"}]},{"title":"自动获取文件里IP的shell脚本","date":"2017-04-09T16:00:00.000Z","path":"2017/04/10/自动获取文件里IP的shell脚本/","text":"在上次写的脚本的基础上进行了改进,因为感觉手动输30个IP还是累。。12345678910111213141516171819#!/bin/bashnum=`wc -l ipad | cut -d ' ' -f 1` while [ $num -gt 0 ] do ipaddr=`sed -n ''$num'p' ipad` touch /etc/sysconfig/network-scripts/ifcfg-eth0:$num ncf=/etc/sysconfig/network-scripts/ifcfg-eth0:$num echo DEVICE=eth0:$num >$ncf echo TYPE=Ethernet >>$ncf echo BOOTPROTO=static >>$ncf echo IPADDR=$ipaddr >>$ncf echo NETMASK=255.255.255.192 >>$ncf echo ONBOOT=yes >>$ncf num=`expr $num - 1` done<ipadservice network restartifconfig 首先建一个名为ipad的空文件并在里面存入需要绑定的IP地址,然后再运行脚本即可","tags":[{"name":"linux","slug":"linux","permalink":"https://dangod.github.io/tags/linux/"},{"name":"运维","slug":"运维","permalink":"https://dangod.github.io/tags/运维/"},{"name":"shell","slug":"shell","permalink":"https://dangod.github.io/tags/shell/"}]},{"title":"Http简介","date":"2017-04-07T16:00:00.000Z","path":"2017/04/08/HTTP简介/","text":"Web浏览器、服务器和相关的Web程序都是通过HTTP相互通信的。 本章主要描述了Web的基础构建与HTTP的核心技术,并使用Python的requests库作为实验的工具。 HTTP简介HTTP使用的是可靠的数据传输协议,它能够保证数据在传输过程中不会被损坏或产生混乱。浏览一个页面时,HTTP客户端(浏览器)发送一个HTTP请求到服务器,服务器在它所存储的内容中去寻找所期望的对象(资源),如果成功,就将对象、对象类型、对象长度以及其他一些信息放在HTTP响应中发送给客户端。 Web资源:Web服务器是Web资源的宿主。最简单的Web资源就是服务器中保存的静态文件:包括视频文件、文本文件等等。资源还可以是根据需要动态生成内容的软件程序。总之,所有类型的内容来源都是资源。 客户端是通过URL来访问资源的,URL描述了一台特定服务器上某资源的特定位置。它们可以明确说明如何从一个精确、固定的位置获取资源。URL都有一种标准格式如下:方案(说明了访问资源所使用的协议类型,这部分通常就是HTTP协议(http://))+因特网地址(www.cc98.org)+服务器上具体资源位置(/specials/a.gif)。) 无状态:HTTP协议是无状态的,即每一个请求/响应周期与前一个都是相互独立的。HTTP协议下,服务器不需要在各次请求之间保留状态信息,好处就是如果一次请求出现了问题,系统不需要做任何的清理。坏处就是,大部分的网络应用都是要求有状态的,开发人员必须通过其他方式来实现有状态,主要是session、cookie以及AJAX。 对象类型(媒体类型):HTTP会为所有的对HTTP象都附加一个MIME类型的数据格式标签。MIME类型是一种文本标记,表示一种主要的对象类型和一个特定的子类型,中间由一条斜杠来分隔,比如HTML格式的文本文档由text/html类型来标记,普通的ASSCII文本文档由text/plain类型来标记,gif格式的图片由image/gif类型来标记等。 Web事务:一个HTTP事务由一条请求命令(从客户端发发往服务器)和一个响应结果(从服务器发回客户端)组成。这种通信是通过名为HTTP报文的格式化数据块进行的。 HTTP支持几种不同的请求命令,这些命令称为HTTP方法(HTTP method),每条HTTP 请求报文都包含一个方法,这个方法会告诉服务器要执行什么动作,5种常见的HTTP方法,GET、PUT、DELETE、POST、HEAD。 每条HTTP响应报文返回时都会携带一个状态码,状态码是一个三位数字,告知客户端请求是否成功,或者是否需要采取其他动作,常见的状态码有:200、302、404。 HTTP报文:Web客户端发往服务器的HTTP报文称为请求报文,从服务器发往客户端的报文称为响应报文。HTTP报文包括三部分: 起始行,在请求报文中用来说明要做些什么,在响应报文中说明出现了什么情况。 首部字段,起始行后面有零个或多个首部字段,每个首部字段都包含一个名字和一个值,为了便于解析两者之间用冒号来分隔。首部以一个空行结束。 主体,空行之后就是课选的报文主体了,其中包含了所有类型的数据。请求主体中包括了要发送给Web服务器的数据;响应主体中装填了要返回给客户端的数据。 连接:HTTP只是个应用层协议,联网传输数据的细节由TCP/IP提供。在HTTP客户端向服务器发送报文之前,需要用网际协议(Internet Protocol, IP)地址和端口号在客户端和服务器之间建立一条TCP/IP连接。 一些重要的Web的结构组件: 代理:位于客户端和服务器之间的HTTP中间实体。 缓存:HTTP的仓库,使常用页面的副本可以保存在离客户端更近的地方。 网关:链接其他应用程序的特殊Web服务器。 隧道:对HTTP通信报文进行盲转发的特殊代理。 Agent代理:发起自动HTTP请求的半智能Web客户端。 接下来详细介绍几个比较重要的概念。 URL服务器资源名被称为统一资源标识符(URI),它有两种格式: URL:通过描述资源的位置来标识资源。 URN:通过名字来识别资源,与资源当前所处位置无关。 HTTP规范将更通用的概念URI作为其资源标识符,但实际上,目前HTTP应用程序处理的只是URI的自己URL,当前技术背景下所说的URI基本上都代表URL。 URL语法大多数URL方案的URL语法都建立在这个通用格式之上:<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>。但几乎没有哪个URL包含了所有这些组件,URL最重要的3个部分是方案(scheme),主机(host)和路径(path)。 URL(“方案://服务器位置/路径”)分为三部分: URL的第一部分(http)是URL方案(scheme),方案可以告知Web客户端怎样访问资源。方案有很多种,比如http://开头表示使用HTTP协议访问,ftp://开头表示使用FTP协议访问资源。 URL的第二部分(www.example.com)是指服务器的位置。 URL的第三部分(/example/example.png)是指资源路径,路径说明了请求的是服务器上哪个特定的本地资源。 URL快捷方式URL客户端可以理解并使用几种URL快捷方式。 绝对和相对URL:绝对URL比较简单,这里就不多说明了。重点是相对URL,它是不完整的,要从相对URL总获取访问资源所需的全部信息,就必须相对于另一个被称为基础URL进行解析。 基础URL一般来自于几个不同的地方: 在资源中显式提供,比如HTML文件中用<base>标记定义一个基础URL。 封装资源的基础URL,如果在一个没有显式指定基础URL的资源中发现了一个相对URL,可以将它所属资源的URL作为基础。 没有基础URL,某些情况下,没有基础URL,意味着可能是出错了。 举个栗子: 相对URL:/a.gif,其基础URL是http://www.example.com/images,则真正解析出来的URL是http://www.example.com/images/a.gif。 自动扩展URL:很多浏览器都会在用户输入URL的时候尝试自动扩展URL,这样用户就只需要输入URL中的关键字,浏览器会自动补全URL。这些自动扩展特性有以下两种方式: 主机名扩展:只要有些关键字,浏览器通常即可以将你输入的主机名扩展为完整的主机名。比如你在地址栏输入google,浏览器会自动插入www.和.com,构建出www.google.com。 历史扩展:浏览器会将用户以前访问过的URL历史存储起来,当你输入URL时,它就可以将你输入的URL与历史记录中的URL前缀进行匹配,并提供一些完整的选项供你选择。 HTTP报文HTTP报文是在HTTP应用程序之间发送的数据块,这些数据以一些文本形式的元信息开头,这些信息详细的描述了报文的内容以及含义,后面跟着可选的数据部分。 报文的组成HTTP报文是简单的格式化数据块,由三个部分组成: 对报文进行描述的起始行(start line):请求报文的起始行包含一个方法和一个请求URL;响应报文的起始行包含HTTP版本、状态码以及原因短语。 包含属性的首部(header):HTTP首部字段向请求和响应报文中添加了一些附加信息,本质上来说,它们只是一些名/值对列表。 首部分为以下几类: 通用首部:可以用在请求报文或者响应报文中,提供一些通用功能,比如提供报文构建时间和日期的Date首部:Date: Tue 3 Oct 1999 02:33:34 GMT 请求首部:请求报文特有,为服务器提供一些额外信息,比如客户端希望接收什么类型的数据。 响应首部:响应报文特有,为客户端提供信息,比如客户端与哪种类型的服务器进行交互。 实体首部:描述主体的长度和内容,或者就是资源本身。 扩展首部:规范中没有定义的新首部,由应用程序开发者创建。 可选的、包含数据的主体(body)部分; 起始行和首部就是由行分隔的ASCII文本,每行都以一个由两个字符组成的行终止序列作为结束,其中包括一个回车符(ASCII码13)和一个换行符(ASCII码10)。这个行终止序列可以写作CRLF。 实体的主体或报文的主体是一个可选的数据块,与起始行和首部不同的是,主体中可以包含文本或二进制数据,也可以为空。 报文的语法HTTP报文分为两类: 请求报文(request message),向Web服务器请求一个动作,格式如下: 123<method><request-URL><version><headers><entity-body> 响应报文(response message),将请求的结果返回给客户端,格式如下: 123<version><status><reason-phrass><headers><entity-body> 以下是对个部分的简要描述: 方法(method):客户端希望服务器对资源执行的动作。常用HTTP方法如下: GET:是最常用的方法,通常用于从服务器获取一份资源,不包含主体。 HEAD:与GET方法类似,但HEAD只从服务器获取资源的首部,不包含主体。使用HEAD可以在不获取资源的情况下了解资源的类型等情况、通过查看响应中的状态码,判断某个资源是否存在、通过查看首部,测试资源是否被修改。 PUT:向服务器写入文档,包含主体。PUT方法的语义就是让服务器用请求的主体部分来创建一个由所请求的URL命名的新文档,或者那个URL已存在的话,用这个请求的主体来代替它。 POST:向服务器输入数据,通常用它来支持HTML表单。 TRACE:对可能经过代理服务器传送到服务器上去的报文进行追踪,不包含主体。客户端发起一个请求时,这个请求可能要穿过防火墙、代理、网关或一些其他应用程序。每个中间节点都可能修改原始的HTTP请求,TRACE请求会在目的服务器端发起一个”环回”诊断,行程最后一站的服务器会弹回一条TRACE响应,并在相应主体中携带它收到的原始请求报文,这样客户端就能够查看原始报文是否、以及如何被损坏或修改过。 OPTIONS:请求Web服务器告知其支持的各种功能,不包含主体,可以询问服务器通常支持哪些方法,或者对某些特殊资源支持哪些方法。 DELETE:请求服务器删除请求URL所指定的资源,但是客户端无法保证删除操作一定会被执行,因为HTTP规范允许服务器在不通知客户端的情况下撤销请求,不包含主体。 请求URL(request-URL):命名了请求的资源,或者URL路径组件的完整URL。 版本(version):报文所使用的HTTP版本,基本格式是HTTP/<major>.<minor>,主要版本号和次要版本号都是整数。 状态码(status-code):这三位数字描述了请求过程中发生的情况。 原因短语(reason-phrase):数字状态码的可读版本,包含行终止序列之前的所有文本。 首部(header):包含一些对资源格式的说明。 实体的主体部分(entity-body):包含一个由任一数据组成的数据块,也可以为空。 有状态的Web应用HTTP协议是无状态的,也就是说,在客户端的各次请求之间,服务器是不会保留状态信息的。每一次请求都被认为是全新的请求。实现有状态的Web应用大致上有以下几种方式: session:人为的在客户端和服务器之间传递会话id。具体做法就是:服务器在发送响应数据给客户端的时候带一个唯一的令牌(token),随后不论何时客户端向服务器发起请求的时候都把这个令牌附加在后面,让服务器能够辨识这个客户端。在 web 开发领域我们把这个来回传递的令牌叫做会话标识符(session identifier)。 cookies:cookie就是在一个请求/响应周期内,服务器发送给客户端并存储在客户端的一段数据。当你第一次访问一个网站的时候,服务器会给你发送会话信息并将其存储在你本地电脑浏览器的 cookie 里。要注意的是真正的会话数据是存在服务器上的。在客户端发起每一个请求的时候,服务器就会比对客户端的 cookie 和服务器上的会话数据,用来标识当前的会话。通过这种方法,当你再次访问同一个网站的时候,服务器就会通过 cookie 和里面的信息来认出你的会话。 AJAX:主要特点就是允许浏览器发送请求和处理响应的时候不用刷新整个页面。这些请求的响应会通过一些回调来处理。你可以这样理解回调,就是你把一些逻辑存放在某个函数里,当某个条件被触发之后再回来执行你前面存放的逻辑。AJAX 请求就像是普通请求:发送到服务器的请求依然跟普通请求一样有着一个 HTTP 请求该有的所有组成部分,并且服务器处理 AJAX 请求的方法跟处理普通请求也是一样的。唯一不同就是,不是通过浏览器刷新来处理响应,而通常由客户端的一些javascript代码来处理。 使用requests模拟HTTP123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172C:\\Users\\amber>pythonPython 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32Type "help", "copyright", "credits" or "license" for more information.# 导入requests>>> import requests# 向百度首页发送get请求,其他请求方法的格式与此语句一致,只需要把get改成其他方法的名称即可,比如r=requests.post(...)。>>> r = requests.get("https://www.baidu.com")# 现在,我们有一个名为 r 的 Response 对象。我们可以从这个对象中获取所有我们想要的信息。# 比如我们想要读取服务器相应的内容,在这里返回的是百度首页的html>>>r.text'<!DOCTYPE html>\\r\\n<!--STATUS OK--><html>...</html>\\r\\n'# 查看requests使用了什么编码>>> r.encoding'ISO-8859-1'# 也能以字节的方式访问请求响应体,看到返回的值是以b''包裹的,表示二进制的形式>>> r.contentb'<!DOCTYPE html>\\r\\n<!--STATUS OK--><html>...</html>\\r\\n'# 也可以检测响应状态码>>> r.status_code200# 也可以查看一个以Python字典形式展示的服务器响应头>>> r.headers{'Content-Type': 'text/html', 'Server': 'bfe/1.0.8.14', 'Content-Encoding': 'gzip', 'Last-Modified': 'Mon, 25 Jul 2016 11:13:20 GMT', 'Transfer-Encoding': 'chunked', 'Date': 'Tue, 06 Sep 2016 03:34:33 GMT', 'Connection': 'keep-alive', 'Set-Cookie':'__bsi=13338092233546665715_00_119_N_N_2_0303_C02F_N_N_N_0; expires=Tue, 06-Sep-16 03:34:38 GMT; domain=www.baidu.com; path=/', 'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Pragma': 'no-cache'}# 我们可以访问这些响应头字段>>> r.headers.get('content-type')'text/html'# 有些网页为了反爬虫,直接用requests进行http请求是会被拒绝的,我们可以为请求添加HTTP头部,伪装成浏览器进行访问,只需要传递一个dict给headers参数就行了# 比如访问知乎,不伪装成浏览器,访问会被拒绝>>> r = requests.get("https://www.zhihu.com")>>> r.text'<html><body><h1>500 Server Error</h1>\\nAn internal server error occured.\\n</body></html>\\n'>>> r = requests.get("https://www.zhihu.com")>>> r.text'<html><body><h1>500 Server Error</h1>\\nAn internal server error occured.\\n</body></html>\\n'>>> r.status_code500>>> url = "https://www.zhihu.com">>> headers = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'}>>> r = requests.get(url, headers=headers)# 使用了请求头以后访问不会被拒绝>>> r.text'<!DOCTYPE html>\\n<html lang="zh-CN" class="">...</html>'# 默认情况下,除了 HEAD, Requests 会自动处理所有重定向。可以使用响应对象的 history 方法来追踪重定向。Response.history 是一个 Response 对象的列表,为了完成请求而创建了这些对象。这个对象列表按照从最老到最近的请求进行排序。# 例如,Github 将所有的 HTTP 请求重定向到 HTTPS:>>> r = requests.get('http://github.com')>>> r.url'https://github.com/'>>> r.status_code200>>> r.history[<Response [301]>]# 如果你使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么你可以通过 allow_redirects 参数禁用重定向处理:>>> r = requests.get('http://github.com', allow_redirects=False)>>> r.status_code301>>> r.history[]# 如果你使用了 HEAD,你也可以启用重定向:>>> r = requests.head('http://github.com', allow_redirects=True)>>> r.url'https://github.com/'>>> r.history[<Response [301]>]","tags":[{"name":"http","slug":"http","permalink":"https://dangod.github.io/tags/http/"}]},{"title":"centos分配IP脚本","date":"2017-04-05T16:00:00.000Z","path":"2017/04/06/centos分配IP脚本/","text":"常常有客户的centos服务器需要分配15个IP甚至30个IP。每次需要手动分配十分麻烦,于是花了一天时间学了shell脚本,写了这个脚本。12345678910111213141516#!/bin/bashread -p "The IP numbers: " numwhile [[ $num -gt 0 ]]do read -p "Please enter IP Address :" ipaddr touch /etc/sysconfig/network-scripts/ifcfg-eth0:$num ncf=/etc/sysconfig/network-scripts/ifcfg-eth0:$num echo DEVICE=eth0:$num >$ncf echo TYPE=Ethernet >>$ncf echo BOOTPROTO=static >>$ncf echo IPADDR=$ipaddr >>$ncf echo NETMASK=255.255.255.192 >>$ncf echo ONBOOT=yes >>$ncf num=`expr $num - 1`doneservice network restart 由于公司的掩码都是255.255.255.192于是直接用了,只要输入需要分配的IP个数和IP地址即可。写完这个脚本之后马上就派上用场了,但是还是觉得手动输入IP太麻烦,下次有时间再写一个直接从另一个文件提取IP地址的脚本吧。","tags":[{"name":"linux","slug":"linux","permalink":"https://dangod.github.io/tags/linux/"},{"name":"运维","slug":"运维","permalink":"https://dangod.github.io/tags/运维/"},{"name":"shell","slug":"shell","permalink":"https://dangod.github.io/tags/shell/"}]}]