Eclipse使用 junit测试, 报 “The builder launch configuration could not be found” 错误
解决方法如下:
- Package Explorer 视图下选中项目名
- 右键项目名,然后选择Properties 或者 在Eclipse 工具栏中选择 Project -> Properties
- 弹出的 Properties 对话框中,点击 Builders
- 将没有勾选,缺失的 builder 移除掉
- 点击 OK 按钮
DONE!
Eclipse使用 junit测试, 报 “The builder launch configuration could not be found” 错误
解决方法如下:
DONE!
ibatis 测试时出现错误提示: “could not find sql statement to include with refid xxx” , 出现这种错误一般是下面两种情况引起:
DONE!
jdk提供了非常方便的方式来生成线程池, 如Executors.newxxxxxx的方式, 实现实际使用的都是以下ThreadPoolExecutor的方法:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
线程池的管理是这样一个过程:
首先创建线程池, 然后根据任务的数量逐步将线程增大到corePoolSize数量, 如果此时任有任务增加, 则放置到workQueue中, 直到workQueue爆满为止, 然后继续增加线程池中的线程数量,增加处理能力,最终达到maximumPoolSize。 如果此时还是有任务增加进来会怎样呢 ? 这就需要handler来处理了,或者丢弃新任务, 或者拒绝新任务,或者挤占已有任务等。在任务队列何线程池都饱和情况下,一旦有线程处于等待(任务处理完毕, 没有新任务增加)状态的时间超过keepAliveTime,则该线程终止, 也就是说池中的线程数量会足部降低, 直至为corePoolSize数量为止。 至于threadFactory,可以自己新增一个,设置线程的自定义名称, daemon状态,便于后续排查错误。
我们用一个测试程序来帮助理解线程池的运行过程:
public class ThreadPoolTest {
private static ExecutorService es = new ThreadPoolExecutor(50, 100, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(100000));
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100000; i++) {
es.execute(() -> {
System.out.print(1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
ThreadPoolExecutor tpe = ((ThreadPoolExecutor) es);
while (true) {
System.out.println();
int queueSize = tpe.getQueue().size();
System.out.println(“当前排队任务数:” + queueSize);
int activeCount = tpe.getActiveCount();
System.out.println(“当前活动线程数:” + activeCount);
long completedTaskCount = tpe.getCompletedTaskCount();
System.out.println(“执行完成线程数:” + completedTaskCount);
long taskCount = tpe.getTaskCount();
System.out.println(“总线程数:” + taskCount);
Thread.sleep(3000);
}
}
}
程序每3秒打印一次线程池情况:
11111111111111111111111111111111111111111111111111
当前排队线程数:99950
当前活动线程数:50
执行完成线程数:0
总线程数:100000
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99849
当前活动线程数:50
执行完成线程数:127
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99700
当前活动线程数:50
执行完成线程数:250
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99550
当前活动线程数:50
执行完成线程数:400
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99400
当前活动线程数:50
执行完成线程数:550
总线程数:100000
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
当前排队线程数:99250
当前活动线程数:50
执行完成线程数:700
总线程数:100000
。。。。。。
可以看到当前活动的线程数永远都是50, 为什么就没有突破?怎么没到100呢? 大家可以思考下,相信看了上面的描述,很容易就能理解了,DONE!
一台机器系统配置ipv6地址和ipv4地址,由于各种原因,有些网站V6的速度比V4差很多, 如果想设置下优先顺序, 该如何设置呢?
Linux设置ipv4优先:
[root@localhost ~]#vi /etc/gai.conf
Windows 平台也有这样的问题,解决的方式有二个,一个是把 Windows 的 IPv6 关闭,另一个可以使用下面的命令设置优先级。
1. 以系统管理者身份执行 命令提示字元,查询连线顺序
netsh interface ipv6 show prefixpolicies
优先顺序 标签 前缀
———- —– ——————————–
50 0 ::1/128
40 1 ::/0
35 4 ::ffff:0:0/96
30 2 2002::/16
5 5 2001::/32
IPv4 ::ffff:0:0/96 的顺序是 35
2. 修改顺序,让 IPv4 优先,数字越大,优先性愈高
netsh interface ipv6 set prefixpolicy ::ffff:0:0/96 60 4
就是以 IPv4 为优先
改回來
netsh interface ipv6 set prefixpolicy ::ffff:0:0/96 35 4
DONE!!!!
使用手机与已经蓝牙配对的mac pro机器传送文件,却总提示失败。 解决方法如下:
mac机器还需要打开共享的权限:
打开“系统偏好设置” -> 选择“共享” -> 勾选左边的“蓝牙共享”,也可以在右侧界面做一些接收文件存放默认路径等的设置。
DONE!
网上发现的一篇很好的关于ubuntu下查看或安装包的方法的文章,转载分享下,原文链接见文末。
说明:由于图形化界面方法(如Add/Remove… 和Synaptic Package Manageer)比较简单,所以这里主要总结在终端通过命令行方式进行的软件包安装、卸载和删除的方法。
一、Ubuntu中软件安装方法
1、APT方式
(1)普通安装:apt-get install softname1 softname2 …;
(2)修复安装:apt-get -f install softname1 softname2… ;(-f Atemp to correct broken dependencies)
(3)重新安装:apt-get –reinstall install softname1 softname2…;
2、Dpkg方式
(1)普通安装:dpkg -i package_name.deb
3、源码安装(.tar、tar.gz、tar.bz2、tar.Z)
首先解压缩源码压缩包然后通过tar命令来完成
a.解xx.tar.gz:tar zxf xx.tar.gz
b.解xx.tar.Z:tar zxf xx.tar.Z
c.解xx.tgz:tar zxf xx.tgz
d.解xx.bz2:bunzip2 xx.bz2
e.解xx.tar:tar xf xx.tar
然后进入到解压出的目录中,建议先读一下README之类的说明文件,因为此时不同源代码包或者预编译包可能存在差异,然后建议使用ls -F –color或者ls -F命令(实际上我的只需要 l 命令即可)查看一下可执行文件,可执行文件会以*号的尾部标志。
一般依次执行./configure
make
sudo make install
即可完成安装。
二、Ubuntu中软件包的卸载方法
1、APT方式
(1)移除式卸载:apt-get remove softname1 softname2 …;(移除软件包,当包尾部有+时,意为安装)
(2)清除式卸载 :apt-get –purge remove softname1 softname2…;(同时清除配置)
清除式卸载:apt-get purge sofname1 softname2…;(同上,也清除配置文件)
2、Dpkg方式
(1)移除式卸载:dpkg -r pkg1 pkg2 …;
(2)清除式卸载:dpkg -P pkg1 pkg2…;
三、Ubuntu中软件包的查询方法
Dpkg 使用文本文件来作为数据库.通称在 /var/lib/dpkg
目录下. 通称在 status 文件中存储软件状态,和控制信息. 在 info/ 目录下备份控制文件, 并在其下的 .list 文件中记录安装文件清单, 其下的 .mdasums 保存文件的 MD5 编码.
体验使用数据库的时刻到了:
$ dpkg -l Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Installed/Config-files/Unpacked/Failed-config/Half-installed |/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad) ||/ Name Version Description +++-===========-================-======================================== ii aalib1 1.4p5-28 ascii art library - transitional package ii adduser 3.85 Add and remove users and groups ii alien .63 install non-native packages with dpkg ... ...
每条记录对应一个软件包, 注意每条记录的第一, 二, 三个字符. 这就是软件包的状态标识, 后边依此是软件包名称, 版本号, 和简单描述.
也可以以统配符模式进行模糊查询, 比如我要查找以nano字符开始的所有软件包:
$ dpkg -l nano* Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Installed/Config-files/Unpacked/Failed-config/Half-installed |/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad) ||/ Name Version Description +++-==============-==============-============================================ ii nano 1.3.10-2 free Pico clone with some new features pn nano-tiny <none> (no description available) un nanoblogger <none> (no description available)
以上状态说明: 系统中安装了 nano
版本为 1.3.10-2
;安装过 nano-tiny
, 后来又清除了; 从未安装过nanoblogger
.
如果觉得 dpkg 的参数过多, 不利于记忆的话, 完全可以使用 dpkg-query 进行 dpkg 数据库查询.
应用范例:
$ dpkg –listfiles nano
or
$ dpkg-query -L nano
$ dpkg -s nano
or
$ dpkg-query -s nano
$ dpkg -l
or
$dpkg-query -l
$ dpkg-query -S nano
or
$ dpkg -S nano
三、其他应用总结
apt-cache search # ——(package 搜索包)
apt-cache show #——(package 获取包的相关信息,如说明、大小、版本等)
apt-get install # ——(package 安装包)
apt-get install # —–(package –reinstall 重新安装包)
apt-get -f install # —–(强制安装, “-f = –fix-missing”当是修复安装吧…)
apt-get remove #—–(package 删除包)
apt-get remove –purge # ——(package 删除包,包括删除配置文件等)
apt-get autoremove –purge # —-(package 删除包及其依赖的软件包+配置文件等(只对6.10有效,强烈推荐))
apt-get update #——更新源
apt-get upgrade #——更新已安装的包
apt-get dist-upgrade # ———升级系统
apt-get dselect-upgrade #——使用 dselect 升级
apt-cache depends #——-(package 了解使用依赖)
apt-cache rdepends # ——(package 了解某个具体的依赖,当是查看该包被哪些包依赖吧…)
apt-get build-dep # ——(package 安装相关的编译环境)
apt-get source #——(package 下载该包的源代码)
apt-get clean && apt-get autoclean # ——–清理下载文件的存档 && 只清理过时的包
apt-get check #——-检查是否有损坏的依赖
dpkg -S filename —–查找filename属于哪个软件包
apt-file search filename —–查找filename属于哪个软件包
apt-file list packagename —–列出软件包的内容
apt-file update –更新apt-file的数据库
dpkg –info “软件包名” –列出软件包解包后的包名称.
dpkg -l –列出当前系统中所有的包.可以和参数less一起使用在分屏查看. (类似于rpm -qa)
dpkg -l |grep -i “软件包名” –查看系统中与”软件包名”相关联的包.
dpkg -s 查询已安装的包的详细信息.
dpkg -L 查询系统中已安装的软件包所安装的位置. (类似于rpm -ql)
dpkg -S 查询系统中某个文件属于哪个软件包. (类似于rpm -qf)
dpkg -I 查询deb包的详细信息,在一个软件包下载到本地之后看看用不用安装(看一下呗).
dpkg -i 手动安装软件包(这个命令并不能解决软件包之前的依赖性问题),如果在安装某一个软件包的时候遇到了软件依赖的问题,可以用apt-get -f install在解决信赖性这个问题.
dpkg -r 卸载软件包.不是完全的卸载,它的配置文件还存在.
dpkg -P 全部卸载(但是还是不能解决软件包的依赖性的问题)
dpkg -reconfigure 重新配置
apt-get install
下载软件包,以及所有依赖的包,同时进行包的安装或升级。如果某个包被设置了 hold (停止标志,就会被搁在一边(即不会被升级)。更多 hold 细节请看下面。
apt-get remove [–purge]
移除 以及任何依赖这个包的其它包。
–purge 指明这个包应该被完全清除 (purged) ,更多信息请看 dpkg -P。
apt-get update
升级来自 Debian 镜像的包列表,如果你想安装当天的任何软件,至少每天运行一次,而且每次修改了
/etc/apt/sources.list 後,必须执行。
apt-get upgrade [-u]
升 级所有已经安装的包为最新可用版本。不会安装新的或移除老的包。如果一个包改变了依赖关系而需要安装一个新的包,那么它将不会被升级,而是标志为 hold。apt-get update 不会升级被标志为 hold 的包 (这个也就是 hold 的意思)。请看下文如何手动设置包为 hold。我建议同时使用 ‘-u’ 选项,因为这样你就能看到哪些包将会被升级。
apt-get dist-upgrade [-u]
和 apt-get upgrade 类似,除了 dist-upgrade 会安装和移除包来满足依赖关系。因此具有一定的危险性。
apt-cache search
在软件包名称和描述中,搜索包含xxx的软件包。
apt-cache show
显示某个软件包的完整的描述。
apt-cache showpkg
显示软件包更多细节,以及和其它包的关系。
dselect
console-apt
aptitude
gnome-apt
APT 的几个图形前端(其中一些在使用前得先安装)。这里 dselect 无疑是最强大的,也是最古老,最难驾驭。
普通 Dpkg 用法
dpkg -i
安装一个 Debian 包文件,如你手动下载的文件。
dpkg -c
列出 的内容。
dpkg -I
从 中提取包信息。
dpkg -r
移除一个已安装的包。
dpkg -P
完全清除一个已安装的包。和 remove 不同的是,remove 只是删掉数据和可执行文件,purge 另外还删除所有的配制文件。
dpkg -L
列出 安装的所有文件清单。同时请看 dpkg -c 来检查一个 .deb 文件的内容。
dpkg -s
显示已安装包的信息。同时请看 apt-cache 显示 Debian 存档中的包信息,以及 dpkg -I 来显示从一个 .deb 文件中提取的包信息。
dpkg-reconfigure
重 新配制一个已经安装的包,如果它使用的是 debconf (debconf 为包安装提供了一个统一的配制界面)。你能够重新配制 debconf 它本身,如你想改变它的前端或提问的优先权。例如,重新配制 debconf,使用一个 dialog 前端,简单运行:
dpkg-reconfigure –frontend=dialog debconf (如果你安装时选错了,这里可以改回来哟:)
echo ” hold” | dpkg –set-selections
设置 的状态为 hlod (命令行方式)
dpkg –get-selections “”
取的 的当前状态 (命令行方式)
支持通配符,如:
Debian:~# dpkg –get-selections *wine*
libwine hold
libwine-alsa hold
libwine-arts hold
libwine-dev hold
libwine-nas hold
libwine-print hold
libwine-twain hold
wine hold
wine+ hold
wine-doc hold
wine-utils hold
例如:
大家现在用的都是 gaim-0.58 + QQ-plugin,为了防止 gaim 被升级,我们可以采用如下方法:
方法一:
Debian:~# echo “gaim hold” | dpkg –set-selections
然後用下面命令检查一下:
Debian:~# dpkg –get-selections “gaim”
gaim hold
现在的状态标志是 hold,就不能被升级了。
如果想恢复怎么办呢?
Debian:~# echo “gaim install” | dpkg –set-selections
Debian:~# dpkg –get-selections “gaim”
gaim install
这时状态标志又被重置为 install,可以继续升级了。
同志们会问,哪个这些状态标志都写在哪个文件中呢?
在 /var/lib/dpkg/status 里,你也可以通过修改这个文件实现 hold。
有时你会发现有的软件状态标志是 purge,不要奇怪。
如:事先已经安装了 amsn,然後把它卸了。
apt-get remove –purge amsn
那么状态标志就从 install 变成 purge。
方法二:
在/etc/apt 下手动建一个 preferences 文件
内容:
Package: gaim
Pin: version 0.58*
保存
dpkg -S
在包数据库中查找 ,并告诉你哪个包包含了这个文件。(注:查找的是事先已经安装的包)
——————————————–
Debian的软件包管理工具命令不完全列表
——————————————–
Debian系统中所有的包信息都在/var/lib/dpkg下.其中/var/lib/dpkg/info目录中保存了各个软件包的信息及管理文件.每个文件的作用如下:
以 “.conffiles” 结尾的文件记录软件包的配置列表.
以 “.list” 结尾的文件记录了软件包的文件列表,用户可在文件当中找到软件包文件的具体安装位置.
以 “.md5sums” 结尾的文件记录了md5信息,用来进行包的验证的.
以 “.config” 结尾的文件是软件包的安装配置角本.
以 “.postinst” 角本是完成Debian包解开之后的配置工作,通常用来执行所安装软件包相关的命令和服务的重新启动.
以 “.preinst” 角本在Debain解包之前运行,主要作用是是停止作用于即将升级的软件包服务直到软件包安装或和升级完成.
以 “.prerm” 脚本负责停止与软件包关联的daemon服务,在删除软件包关联文件之前执行.
以 “.postrm” 脚本负责修改软件包链接或文件关联,或删除由它创建的文件.
/var/lib/dpkg/available是软件包的描述信息.
包括当前系统中所有使用的Debian安装源中所有的软件包,还包括当前系统中已经安装和未安装的软件包.
1.dpkg包管理工具
dpkg -r 卸载软件包.不是完全的卸载,它的配置文件还存在.
dpkg –info “软件包名” –列出软件包解包后的包名称.
dpkg -l –列出当前系统中所有的包.可以和参数less一起使用在分屏查看.
dpkg -l |grep -i “软件包名” –查看系统中与”软件包名”相关联的包.
dpkg -s 查询已安装的包的详细信息. dpkg -L 查询系统中已安装的软件包所安装的位置.
dpkg -S 查询系统中某个文件属于哪个软件包.
dpkg -I 查询deb包的详细信息,在一个软件包下载到本地之后看看用不用安装(看一下呗).
dpkg -i 手动安装软件包(这个命令并不能解决软件包之前的依赖性问题),如果在安装某一个软件包的时候遇到了软件依赖的问题,可以用apt-get -f install在解决信赖性这个问题.
dpkg -reconfigure 重新配置
dpkg -P 全部卸载(但是还是不能解决软件包的依赖性的问题)
2. apt高级包管理工具
(1)GTK图形的”synaptic”,这是APT的前端工具.
(2)”aptitude”,这也是APT的前端工具.
用APT管理工具进行包的管理,可以有以下几种方法做源:
(1)拿安装盘做源,方法如下:
apt-cdrom ident 扫描光盘的信息
apt-cdrom add 添加光盘源
(2)这也是最常用的方法就是把源添加到/etc/apt/source.list中,之后更新列apt-get update
APT管理工具常用命令
apt-cache 加上不同的子命令和参数的使用可以实现查找,显示软件,包信息及包信赖关系等功能.
apt-cache stats 显示当前系统所有使用的Debain数据源的统计信息.
apt-cache search +”包名”,可以查找相关的软件包.
apt-cache show +”包名”,可以显示指定软件包的详细信息.
apt-cache depends +”包名”,可以查找软件包的依赖关系.
apt-get upgrade 更新系统中所有的包到最新版
apt-get install 安装软件包
apt-get –reindtall install 重新安装软件包
apt-get remove 卸载软件包
apt-get –purge remove 完全卸载软件包
apt-get clean 清除无用的软件包
在用命令apt-get install之前,是先将软件包下载到/var/cache/apt/archives中,之后再进行安装的.所以我们可以用apt-get clean清除/var/cache/apt/archives目录中的软件包.
源码包安装
apt-cache showsrc 查找看源码包的文件信息(在下载之前)
apt-get source 下载源码包.
apt-get build-dep +”包名” 构建源码包的编译环境.
清除处于rc状态的软件包
dpkg -l |grep ^rc|awk ‘{print $2}’ |tr [“\n”] [” “] | sudo xargs dpkg -P –
转自: http://www.cnblogs.com/forward/archive/2012/01/10/2318483.html
下载保存后的CSDN页面,再次打开会跳转CSDN首页,可以使用下面方式解决:
使用编辑器打开下载的html页面,找到类似下面的行:
<div style=”display:none;”>
<img src=”https://blog.csdn.net/robinvista/article/details/96437123″ onerror=”setTimeout(function(){if(!/(csdn.net|iteye.com|baiducontent.com|googleusercontent.com|360webcache.com|sogoucdn.com|bingj.com|baidu.com)$/.test(window.location.hostname)){window.location.href="\x68\x74\x74\x70\x73\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x73\x64\x6e\x2e\x6e\x65\x74"}},3000);”>
</div>
删除后保存即可,DONE!
ubuntu 16.04 QT编译c++项目产生的二进制文件执行时出现下面提示: Segmentation Fault (“Core dumped”),
先确认下是否是权限不足造成的,使用root用户运行下编译后的二进制文件!!!
如果不行,再尝试下下面的方法:
分段错误是指您的系统尝试访问不存在的内存页面。核心转储意味着当一部分代码尝试在只读或空闲位置执行读写操作时。段错误通常与名为 core 的文件相关联,它通常发生在升级过程中。
在核心转储期间运行某些命令时,您可能会遇到“无法打开锁定文件”,这是因为系统正在尝试捕获不存在的位块,这是由于某些特定程序的二进制文件崩溃.
您可以通过回溯或调试来解决它,但解决方案是修复损坏的包,我们可以通过执行以下步骤来完成:
Step 1: Remove the lock files present at different locations.
sudo rm -rf /var/lib/apt/lists/lock /var/cache/apt/archives/lock /var/lib/dpkg/lock
Step 2: Remove repository cache.
sudo apt-get clean all
Step 3: Update and upgrade your repository cache.
sudo apt-get update && sudo apt-get upgrade
Step 4: Now upgrade your distribution, it will update your packages.
sudo apt-get dist-upgrade
Step 5: Find the broken packages and delete them forcefully.
sudo dpkg -l | grep ^..r | apt-get purge
我运行完上面的步骤就OK了,希望可以帮到需要的人!
重温下敏捷开发概念!
敏捷开发(agile development)是非常流行的软件开发方法。据统计,2018年90%的软件开发采用敏捷开发。
但是,到底什么是敏捷开发,能说清的人却不多。本文尝试用简洁易懂的语言,解释敏捷开发。
敏捷开发的核心是迭代开发(iterative development)。敏捷一定是采用迭代开发的方式。
那么什么是”迭代开发”呢?迭代的英文是 iterative,直译为”重复”,迭代开发其实就是”重复开发”。
对于大型软件项目,传统的开发方式是采用一个大周期(比如一年)进行开发,整个过程就是一次”大开发”;迭代开发的方式则不一样,它将开发过程拆分成多个小周期,即一次”大开发”变成多次”小开发”,每次小开发都是同样的流程,所以看上去就好像重复在做同样的步骤。
举例来说,SpaceX 公司想造一个大推力火箭,将人类送到火星。但是,它不是一开始就造大火箭,而是先造一个最简陋的小火箭 Falcon 1。结果,第一次发射就爆炸了,直到第四次发射,才成功进入轨道。然后,开发了中型火箭 Falcon 9,九年中发射了70次。最后,才开发 Falcon 重型火箭。如果 SpaceX 不采用迭代开发,它可能直到现在还无法上天。
迭代开发将一个大任务,分解成多次连续的开发,本质就是逐步改进。开发者先快速发布一个有效但不完美的最简版本,然后不断迭代。每一次迭代都包含规划、设计、编码、测试、评估五个步骤,不断改进产品,添加新功能。通过频繁的发布,以及跟踪对前一次迭代的反馈,最终接近较完善的产品形态。
迭代开发只是要求将开发分成多个迭代,并没有回答一个重要的问题:怎么划分迭代,哪个任务在这个迭代,哪个任务在下个迭代?这时,一般采用”增量开发”(incremental development)划分迭代。
所谓”增量开发”,指的是软件的每个版本,都会新增一个用户可以感知的完整功能。也就是说,按照新增功能来划分迭代。
举例来说,房产公司开发一个10栋楼的小区。如果采用增量开发的模式,该公司第一个迭代就是交付一号楼,第二个迭代交付二号楼……每个迭代都是完成一栋完整的楼。而不是第一个迭代挖好10栋楼的地基,第二个迭代建好每栋楼的骨架,第三个迭代架设屋顶……
增量开发加上迭代开发,才算真正的敏捷开发。
敏捷开发的第一个好处,就是早期交付,从而大大降低成本。
还是以上一节的房产公司为例,如果按照传统的”瀑布开发模式”,先挖10栋楼的地基、再盖骨架、然后架设屋顶,每个阶段都等到前一个阶段完成后开始,可能需要两年才能一次性交付10栋楼。也就是说,如果不考虑预售,该项目必须等到两年后才能回款。
敏捷开发是六个月后交付一号楼,后面每两个月交付一栋楼。因此,半年就能回款10%,后面每个月都会有现金流,资金压力就大大减轻了。
敏捷开发的第二个好处是,及时了解市场需求,降低产品不适用的风险。
请想一想,哪一种情况损失比较小:10栋楼都造好以后,才发现卖不出去,还是造好第一栋楼,就发现卖不出去,从而改进或停建后面9栋楼?
对于软件项目来说,先有一个原型产品,了解市场的接受程度,往往是项目成功的关键。有一本书叫做《梦断代码》,副标题就是”20+个程序员,三年时间,4732个bug,100+万美元,最后失败的故事”,这就是没有采用敏捷开发的结果。相反的,Instagram 最初是一个地理位置打卡 App,后来发现用户不怎么在乎地理位置,更喜欢上传照片,就改做照片上传软件,结果成了独角兽。
由于敏捷开发可以不断试错,找出对业务最重要的功能,然后通过迭代,调整软件方向。相比传统方式,大大增加了产品成功的可能性。如果市场需求不确定,或者你对该领域不熟悉,那么敏捷开发几乎是唯一可行的应对方式。
虽然敏捷开发将软件开发分成多个迭代,但是也要求,每次迭代都是一个完整的软件开发周期,必须按照软件工程的方法论,进行正规的流程管理。
具体来说,每次迭代都必须依次完成以下五个步骤。
- 需求分析(requirements analysis)
- 设计(design)
- 编码(coding)
- 测试(testing)
- 部署和评估(deployment / evaluation)
每个迭代大约持续2~6周。
《敏捷软件开发宣言》里面提到四个价值观。
- 程序员的主观能动性,以及程序员之间的互动,优于既定流程和工具。
- 软件能够运行,优于详尽的文档。
- 跟客户的密切协作,优于合同和谈判。
- 能够响应变化,优于遵循计划。
该宣言还提出十二条敏捷开发的原则。
转自: https://www.ruanyifeng.com/blog/2019/03/agile-development.html
重温下redis集群的三种集群方案,分享下网络上的一篇文章,写的很好, 原文链接见文末。
在开发测试环境中,我们一般搭建Redis的单实例来应对开发测试需求,但是在生产环境,如果对可用性、可靠性要求较高,则需要引入Redis的集群方案。虽然现在各大云平台有提供缓存服务可以直接使用,但了解一下其背后的实现与原理总还是有些必要(比如面试), 本文就一起来学习一下Redis的几种集群方案。
Redis支持三种集群方案
主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(slave),如下图
客户端可对主数据库进行读写操作,对从数据库进行读操作,主数据库写入的数据会实时自动同步给从数据库。
具体工作机制为:
本示例基于Redis 5.0.3版。
redis.conf的主要配置
###网络相关###
# bind 127.0.0.1 # 绑定监听的网卡IP,注释掉或配置成0.0.0.0可使任意IP均可访问
protected-mode no # 关闭保护模式,使用密码访问
port 6379 # 设置监听端口,建议生产环境均使用自定义端口
timeout 30 # 客户端连接空闲多久后断开连接,单位秒,0表示禁用
###通用配置###
daemonize yes # 在后台运行
pidfile /var/run/redis_6379.pid # pid进程文件名
logfile /usr/local/redis/logs/redis.log # 日志文件的位置
###RDB持久化配置###
save 900 1 # 900s内至少一次写操作则执行bgsave进行RDB持久化
save 300 10
save 60 10000
# 如果禁用RDB持久化,可在这里添加 save ""
rdbcompression yes #是否对RDB文件进行压缩,建议设置为no,以(磁盘)空间换(CPU)时间
dbfilename dump.rdb # RDB文件名称
dir /usr/local/redis/datas # RDB文件保存路径,AOF文件也保存在这里
###AOF配置###
appendonly yes # 默认值是no,表示不使用AOF增量持久化的方式,使用RDB全量持久化的方式
appendfsync everysec # 可选值 always, everysec,no,建议设置为everysec
###设置密码###
requirepass 123456 # 设置复杂一点的密码
部署主从复制模式只需稍微调整slave的配置,在redis.conf中添加
replicaof 127.0.0.1 6379 # master的ip,port
masterauth 123456 # master的密码
replica-serve-stale-data no # 如果slave无法与master同步,设置成slave不可读,方便监控脚本发现问题
本示例在单台服务器上配置master端口6379,两个slave端口分别为7001,7002,启动master,再启动两个slave
[root@dev-server-1 master-slave]# redis-server master.conf
[root@dev-server-1 master-slave]# redis-server slave1.conf
[root@dev-server-1 master-slave]# redis-server slave2.conf
进入master数据库,写入一个数据,再进入一个slave数据库,立即便可访问刚才写入master数据库的数据。如下所示
[root@dev-server-1 master-slave]# redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> set site blog.jboost.cn
OK
127.0.0.1:6379> get site
"blog.jboost.cn"
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=7001,state=online,offset=13364738,lag=1
slave1:ip=127.0.0.1,port=7002,state=online,offset=13364738,lag=0
...
127.0.0.1:6379> exit
[root@dev-server-1 master-slave]# redis-cli -p 7001
127.0.0.1:7001> auth 123456
OK
127.0.0.1:7001> get site
"blog.jboost.cn"
执行info replication
命令可以查看连接该数据库的其它库的信息,如上可看到有两个slave连接到master
优点:
缺点:
哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。如图
哨兵顾名思义,就是来为Redis集群站哨的,一旦发现问题能做出相应的应对处理。其功能包括
哨兵模式的具体工作机制:
在配置文件中通过 sentinel monitor <master-name> <ip> <redis-port> <quorum>
来定位master的IP、端口,一个哨兵可以监控多个master数据库,只需要提供多个该配置项即可。哨兵启动后,会与要监控的master建立两条连接:
_sentinel_:hello
频道与获取其他监控该master的哨兵节点信息与master建立连接后,哨兵会执行三个操作:
_sentinel_:hello
频道发送自己的信息发送INFO命令可以获取当前数据库的相关信息从而实现新节点的自动发现。所以说哨兵只需要配置master数据库信息就可以自动发现其slave信息。获取到slave信息后,哨兵也会与slave建立两条连接执行监控。通过INFO命令,哨兵可以获取主从数据库的最新信息,并进行相应的操作,比如角色变更等。
接下来哨兵向主从数据库的_sentinel_:hello频道发送信息与同样监控这些数据库的哨兵共享自己的信息,发送内容为哨兵的ip端口、运行id、配置版本、master名字、master的ip端口还有master的配置版本。这些信息有以下用处:
如果被PING的数据库或者节点超时(通过 sentinel down-after-milliseconds master-name milliseconds
配置)未回复,哨兵认为其主观下线(sdown,s就是Subjectively —— 主观地)。如果下线的是master,哨兵会向其它哨兵发送命令询问它们是否也认为该master主观下线,如果达到一定数目(即配置文件中的quorum)投票,哨兵会认为该master已经客观下线(odown,o就是Objectively —— 客观地),并选举领头的哨兵节点对主从系统发起故障恢复。若没有足够的sentinel进程同意master下线,master的客观下线状态会被移除,若master重新向sentinel进程发送的PING命令返回有效回复,master的主观下线状态就会被移除
哨兵认为master客观下线后,故障恢复的操作需要由选举的领头哨兵来执行,选举采用Raft算法:
选出领头哨兵后,领头者开始对系统进行故障恢复,从出现故障的master的从数据库中挑选一个来当选新的master,选择规则如下:
挑选出需要继任的slave后,领头哨兵向该数据库发送命令使其升格为master,然后再向其他slave发送命令接受新的master,最后更新数据。将已经停止的旧的master更新为新的master的从数据库,使其恢复服务后以slave的身份继续运行。
本示例基于Redis 5.0.3版。
哨兵模式基于前文的主从复制模式。哨兵的配置文件为sentinel.conf,在文件中添加
sentinel monitor mymaster 127.0.0.1 6379 1 # mymaster定义一个master数据库的名称,后面是master的ip, port,1表示至少需要一个Sentinel进程同意才能将master判断为失效,如果不满足这个条件,则自动故障转移(failover)不会执行
sentinel auth-pass mymaster 123456 # master的密码
sentinel down-after-milliseconds mymaster 5000 # 5s未回复PING,则认为master主观下线,默认为30s
sentinel parallel-syncs mymaster 2 # 指定在执行故障转移时,最多可以有多少个slave实例在同步新的master实例,在slave实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长
sentinel failover-timeout mymaster 300000 # 如果在该时间(ms)内未能完成故障转移操作,则认为故障转移失败,生产环境需要根据数据量设置该值
一个哨兵可以监控多个master数据库,只需按上述配置添加多套
分别以26379,36379,46379端口启动三个sentinel
[root@dev-server-1 sentinel]# redis-server sentinel1.conf --sentinel
[root@dev-server-1 sentinel]# redis-server sentinel2.conf --sentinel
[root@dev-server-1 sentinel]# redis-server sentinel3.conf --sentinel
也可以使用redis-sentinel sentinel1.conf
命令启动。此时集群包含一个master、两个slave、三个sentinel,如图,
我们来模拟master挂掉的场景,执行 kill -9 3017
将master进程干掉,进入slave中执行 info replication
查看,
[root@dev-server-1 sentinel]# redis-cli -p 7001
127.0.0.1:7001> auth 123456
OK
127.0.0.1:7001> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:7002
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
# 省略
127.0.0.1:7001> exit
[root@dev-server-1 sentinel]# redis-cli -p 7002
127.0.0.1:7002> auth 123456
OK
127.0.0.1:7002> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=7001,state=online,offset=13642721,lag=1
# 省略
可以看到slave 7002已经成功上位晋升为master(role:master),接收一个slave 7001的连接。此时查看slave2.conf配置文件,发现replicaof
的配置已经被移除了,slave1.conf的配置文件里replicaof 127.0.0.1 6379
被改为 replicaof 127.0.0.1 7002
。重新启动master,也可以看到master.conf配置文件中添加了replicaof 127.0.0.1 7002
的配置项,可见大哥(master)下位后,再出来混就只能当当小弟(slave)了,三十年河东三十年河西。
优点:
缺点:
哨兵模式解决了主从复制不能自动故障转移,达不到高可用的问题,但还是存在难以在线扩容,Redis容量受限于单机配置的问题。Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题。如图
Cluster采用无中心结构,它的特点如下:
Cluster模式的具体工作机制:
Cluster模式集群节点最小配置6个节点(3主3从,因为需要半数以上),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。
本示例基于Redis 5.0.3版。
Cluster模式的部署比较简单,首先在redis.conf中
port 7100 # 本示例6个节点端口分别为7100,7200,7300,7400,7500,7600
daemonize yes # r后台运行
pidfile /var/run/redis_7100.pid # pidfile文件对应7100,7200,7300,7400,7500,7600
cluster-enabled yes # 开启集群模式
masterauth passw0rd # 如果设置了密码,需要指定master密码
cluster-config-file nodes_7100.conf # 集群的配置文件,同样对应7100,7200等六个节点
cluster-node-timeout 15000 # 请求超时 默认15秒,可自行设置
分别以端口7100,7200,7300,7400,7500,7600 启动六个实例(如果是每个服务器一个实例则配置可一样)
[root@dev-server-1 cluster]# redis-server redis_7100.conf
[root@dev-server-1 cluster]# redis-server redis_7200.conf
...
然后通过命令将这个6个实例组成一个3主节点3从节点的集群,
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7100 127.0.0.1:7200 127.0.0.1:7300 127.0.0.1:7400 127.0.0.1:7500 127.0.0.1:7600 -a passw0rd
执行结果如图
可以看到 7100, 7200, 7300 作为3个主节点,分配的slot分别为 0-5460, 5461-10922, 10923-16383, 7600作为7100的slave, 7500作为7300的slave,7400作为7200的slave。
我们连接7100设置一个值
[root@dev-server-1 cluster]# redis-cli -p 7100 -c -a passw0rd
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:7100> set site blog.jboost.cn
-> Redirected to slot [9421] located at 127.0.0.1:7200
OK
127.0.0.1:7200> get site
"blog.jboost.cn"
127.0.0.1:7200>
注意添加 -c 参数表示以集群模式,否则报 (error) MOVED 9421 127.0.0.1:7200
错误, 以 -a 参数指定密码,否则报(error) NOAUTH Authentication required
错误。
从上面命令看到key为site算出的slot为9421,落在7200节点上,所以有Redirected to slot [9421] located at 127.0.0.1:7200
,集群会自动进行跳转。因此客户端可以连接任何一个节点来进行数据的存取。
通过cluster nodes
可查看集群的节点信息
127.0.0.1:7200> cluster nodes
eb28aaf090ed1b6b05033335e3d90a202b422d6c 127.0.0.1:7500@17500 slave c1047de2a1b5d5fa4666d554376ca8960895a955 0 1584165266071 5 connected
4cc0463878ae00e5dcf0b36c4345182e021932bc 127.0.0.1:7400@17400 slave 5544aa5ff20f14c4c3665476de6e537d76316b4a 0 1584165267074 4 connected
dbbb6420d64db22f35a9b6fa460b0878c172a2fb 127.0.0.1:7100@17100 master - 0 1584165266000 1 connected 0-5460
d4b434f5829e73e7e779147e905eea6247ffa5a2 127.0.0.1:7600@17600 slave dbbb6420d64db22f35a9b6fa460b0878c172a2fb 0 1584165265000 6 connected
5544aa5ff20f14c4c3665476de6e537d76316b4a 127.0.0.1:7200@17200 myself,master - 0 1584165267000 2 connected 5461-10922
c1047de2a1b5d5fa4666d554376ca8960895a955 127.0.0.1:7300@17300 master - 0 1584165268076 3 connected 10923-16383
我们将7200通过 kill -9 pid
杀死进程来验证集群的高可用,重新进入集群执行cluster nodes
可以看到7200 fail了,但是7400成了master,重新启动7200,可以看到此时7200已经变成了slave。
优点:
缺点:
Redis Cluster模式不建议使用pipeline和multi-keys操作,减少max redirect产生的场景。
本文介绍了Redis集群方案的三种模式,其中主从复制模式能实现读写分离,但是不能自动故障转移;哨兵模式基于主从复制模式,能实现自动故障转移,达到高可用,但与主从复制模式一样,不能在线扩容,容量受限于单机的配置;Cluster模式通过无中心化架构,实现分布式存储,可进行线性扩展,也能高可用,但对于像批量操作、事务操作等的支持性不够好。三种模式各有优缺点,可根据实际场景进行选择。
转自:https://segmentfault.com/a/1190000022028642
一般Python 2 >=2.7.9的版本都自带了pip,但有些时候因为这样那样的原因卸载了pip模块,可以使用下面的方式安装(先下载get-pip.py文件,再安装):
1. 如果python是 2.7.x版本
[root@ip o-u-u]# curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py & python get-pip.py
或:
python -m ensurepip –default-pip
2. python >=3.6
[root@ip o-u-u]# curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py & python get-pip.py
使用intellj idea 启动tomcat测试一个简单war包,测试里面一个spring mvc 页面, 使用jstl方式时出现一个怪问题,记录下:
index.jsp页面内容如下:
<%@ page import="java.util.Date" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Index</title> </head> <body> <p> Hello Spring MVC!!! </p> <p> <% Date now1 = new Date(); %> 服务器时间1: <fmt:formatDate value="<%=now1%>" pattern="yyyy-MM-dd HH:mm:ss" /> </p> <p> 服务器时间2: <fmt:formatDate value="${now2}" pattern="yyyy-MM-dd HH:mm:ss" /> </p> </body> </html> IndexController内容:
@Controller public class IndexController { @RequestMapping(value = {"", "/index"}) public ModelAndView dicts() { ModelAndView mv = new ModelAndView("index"); mv.addObject("now2", new Date()); return mv; } }
测试结果,出现错误:
Unable to convert string [${now2}] to class [java.util.Date] for attribute [value]: [Property Editor not registered with the PropertyEditorManager]
如果把jsp页面中 “服务器时间2xxxx” 这行注释掉又一切正常,这就奇怪了。
后来在网上找到解决方案:发现有由于web.xm
声明的问题:
原来声明:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
这样写有问题,修改为:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" metadata-complete="true" version="3.0"> 。。。。 </web-app>
便不会出现这个问题了。网上有说是因为Tomcat的支持web.xml的Servlet是2.5版本。
记录下!
DONE!
要解决此问题,请安装Xcode。如果您从事iOS App开发,则可以安装完整版本,它也将解决此问题。从Apple developers page下载Xcode .dmg文件。
如果你不使用xcode,则只需搜索Xcode的Command Line Tools(命令行工具包),然后下载.dmg文件并安装。
如果您不想下载任何内容,而只想在终端中运行一些命令来解决此问题,那么以下是适合您的命令:
xcode-select --install
这是可以在终端中运行的命令,它将为Xcode安装命令行工具。当您运行此命令时,系统将提示您接受许可证,同意条款和条件等。
也可以尝试下下面的命令后再使用上面的命令解决该问题:
xcode-select --reset
尽管这可以解决您的问题,但作为后续步骤,您可能需要设置命令行工具在不使用Xcode的情况下运行的路径。
xcode-select --switch /Library/Developer/CommandLineTools
如果在运行上述任何命令时遇到权限问题,请sudo
与这些命令一起使用。例如,
sudo xcode-select --switch /Library/Developer/CommandLineTools
如果您已安装Xcode应用程序,请尝试运行以下命令:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
想使用 java -jar xxx.jar 命令行直接执行一个可执行的jar包,而这个xxx.jar包是包含第三方的依赖包的,网上有许多文章描述如何修改pom.xml,但实际是没什么效果的,实验下来如下方法可以达到想要的效果:
<build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>com.ouu.App</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins> </build>
mvn clean compile assembly:single
这是在项目target目录下会产生一个xxxx-jar-with-dependencies.jar的可执行文件
实现方法有如下几种:
(1)第一种方法:
从左向右截取最后一个string后的字符串
${varible##*string}
从左向右截取第一个string后的字符串
${varible#*string}
从右向左截取最后一个string后的字符串
${varible%%string*}
从右向左截取第一个string后的字符串
${varible%string*}
“*”只是一个通配符可以不要
请看下面的例子:
$ MYVAR=foodforthought.jpg $ echo ${MYVAR##*fo} rthought.jpg $ echo ${MYVAR#*fo} odforthought.jpg
(2)第二种方法:
${varible:n1:n2}:截取变量varible从n1开始的n2个字符,组成一个子字符串。可以根据特定字符偏移和长度,使用另一种形式的变量扩展,来选择特定子字符串。试着在 bash 中输入以下行:
$ EXCLAIM=cowabunga $ echo ${EXCLAIM:0:3} cow $ echo ${EXCLAIM:3:7} abunga
这种形式的字符串截断非常简便,只需用冒号分开来指定起始字符和子字符串长度。
比如获取后缀名
ls -al | cut -d “.” -f2
小结:shell对应字符串的处理方法很多,根据需求灵活选择。
在做shell批处理程序时候,经常会涉及到字符串相关操作。有很多命令语句,如:awk,sed都可以做字符串各种操作。 其实shell内置一系列操作符号,可以达到类似效果,大家知道,使用内部操作符会省略启动外部程序等时间,因此速度会非常的快。
一、判断读取字符串值
表达式 含义
${var} 变量var的值, 与$var相同 ${var-DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 * ${var:-DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 * ${var=DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 * ${var:=DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 * ${var+OTHER} 如果var声明了, 那么其值就是$OTHER, 否则就为null字符串 ${var:+OTHER} 如果var被设置了, 那么其值就是$OTHER, 否则就为null字符串 ${var?ERR_MSG} 如果var没被声明, 那么就打印$ERR_MSG * ${var:?ERR_MSG} 如果var没被设置, 那么就打印$ERR_MSG * ${!varprefix*} 匹配之前所有以varprefix开头进行声明的变量 ${!varprefix@} 匹配之前所有以varprefix开头进行声明的变量
加入了“*” 不是意思是: 当然, 如果变量var已经被设置的话, 那么其值就是$var.
二、字符串操作(长度,读取,替换)
表达式 含义
${#string} $string的长度 ${string:position} 在$string中, 从位置$position开始提取子串 ${string:position:length} 在$string中, 从位置$position开始提取长度为$length的子串 ${string#substring} 从变量$string的开头, 删除最短匹配$substring的子串 ${string##substring} 从变量$string的开头, 删除最长匹配$substring的子串 ${string%substring} 从变量$string的结尾, 删除最短匹配$substring的子串 ${string%%substring} 从变量$string的结尾, 删除最长匹配$substring的子串 ${string/substring/replacement} 使用$replacement, 来代替第一个匹配的$substring ${string//substring/replacement} 使用$replacement, 代替所有匹配的$substring ${string/#substring/replacement} 如果$string的前缀匹配$substring, 那么就用$replacement来代替匹配到的$substring ${string/%substring/replacement} 如果$string的后缀匹配$substring, 那么就用$replacement来代替匹配到的$substring
说明:”* $substring”可以是一个正则表达式.
实例:
读取:
1,取得字符串长度
2,字符串所在位置
这个方法让我想起来了js的indexOf,各种语言对字符串的操作方法大方向都差不多,如果有语言基础的话,学习shell会很快的。
3,从字符串开头到子串的最大长度
个人觉得这个函数的用处不大,为什么要从开头开始呢。
4,字符串截取
上面的方法让我想起了,php的substr函数,后面截取的规则是一样的。
5,匹配显示内容
这里括号的用法,是不是根其他的括号用法有相似之处呢,
6,截取不匹配的内容
这里要注意,必须从字符串的第一个字符开始,或者从最后一个开始,可以这样记忆, 井号(#)通常用于表示一个数字,它是放在前面的;百分号(%)卸载数字的后面; 或者这样记忆,在键盘布局中,井号(#)总是位于百分号(%)的左边(即前面) 。
7,匹配并且替换
8. 比较
9. 连接