Bash function 还能这么玩

今天看到一篇讲 Bash function 的有意思的文章,原文在这里

在 Bash 中一般我们这么定义一个函数:

function name () {
  ...
}

这是非常常见的写法,包括我自己在内,一直把他当做类似 Python、C 等语言一样的函数定义语法。实际上这里{ ... }并不代表函数体或者函数的作用域。它只是代表里面的内容是一组命令的集合。了解这点之后,接下来就有一些比较好玩的写法了。

比如下面的函数作用是测试文件是否存在,这里就没用大括号:

function fileExists () [[ -f $1 ]]

或者

function isEven () (( $1 % 2 == 0 ))

还有下面的用法:

function name () (
  ...
)

这里用小括号,当执行函数的时候,会 fork 一个子进程来执行里面的命令。子进程对环境的修改不会影响到外面的父进程,也就不需要保存现场或者恢复现场的操作了。比如设置一些参数:

function caseInsensitiveMatch () (
    shopt -s nocasematch
    ....
)

除了上面的写法,这个用法的前提是函数体仅包含一行命令,或复杂或简单,比如 while、for、if、case 等结构都是可以的:

function sleep1 () while :; do "$@"; sleep 1; done

非插件实现面包屑导航功能

最近开始整理 WordPress 的插件,发现有些插件功能越来越重,比如 Yoast SEO 的插件,出于个人洁癖就把他删除了。但是目前网站原来的 Breadcrumbs 导航功能是这个插件提供的,所以就开始查找替换方案。网上搜索发现一片文章介绍的比较具体 --- 《WordPress实现面包屑导航的方法》,博主其实也是参考了国外网友的非插件实现

以下代码都是基于 Dimox 的原创,我只是在他基础上做了微调,改动不大:

/*
 * Show breadcrumb by Dimox
 * URL: http://dimox.net/wordpress-breadcrumbs-without-a-plugin/
 * Version: 2017.21.01
 * License: MIT
 */
function dangopress_breadcrumb()
{
    /* === OPTIONS === */
    $text['home']     = '首页'; // text for the 'Home' link
    $text['category'] = '%s'; // text for a category page
    $text['search']   = '"%s" 的搜索结果'; // text for a search results page
    $text['tag']      = '包含标签 "%s" 的文章'; // text for a tag page
    $text['404']      = '页面未到到'; // text for the 404 page
    $text['page']     = 'Page %s'; // text 'Page N'
    $text['cpage']    = 'Comment Page %s'; // text 'Comment Page N'

    $prefix         = '<i class="icon-windows"></i>'; // Prefix the breadcrumb
    $wrap_before    = '<div class="breadcrumbs" id="site-breadcrumbs" itemscope itemtype="http://schema.org/BreadcrumbList">'; // the opening wrapper tag
    $wrap_after     = '</div><!-- .breadcrumbs -->'; // the closing wrapper tag
    $sep            = '<i class="icon-caret-right"></i>'; // separator between crumbs
    $sep_before     = '<span class="sep">'; // tag before separator
    $sep_after      = '</span>'; // tag after separator
    $show_home_link = 0; // 1 - show the 'Home' link, 0 - don't show
    $show_current   = 1; // 1 - show current page title, 0 - don't show
    $before         = '<h1 class="current-crumb">'; // tag before the current crumb
    $after          = '</h1>'; // tag after the current crumb
    /* === END OF OPTIONS === */

    global $post;
    $home_url       = home_url('/');

    $link_before    = '<span itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem">';
    $link_after     = '</span>';
    $link_attr      = ' itemprop="item"';
    $link_in_before = '<span itemprop="name">';
    $link_in_after  = '</span>';
    $link           = $link_before . '<a href="%1$s"' . $link_attr . '>' . $link_in_before . '%2$s' . $link_in_after . '</a>' . $link_after;

    $frontpage_id   = get_option('page_on_front');
    $parent_id      = ($post) ? $post->post_parent : '';
    $sep            = ' ' . $sep_before . $sep . $sep_after . ' ';
    $home_link      = $link_before . '<a rel="nofollow" href="' . $home_url . '"' . $link_attr . ' class="home">' . $link_in_before . $text['home'] . $link_in_after . '</a>' . $link_after;

    if (is_home() || is_front_page()) {
        return;
    } else {
        echo $wrap_before . $prefix;
        if ($show_home_link) echo $home_link;

        if ( is_category() ) {
            $cat = get_category(get_query_var('cat'), false);
            if ($cat->parent != 0) {
                $cats = get_category_parents($cat->parent, TRUE, $sep);
                $cats = preg_replace("#^(.+)$sep$#", "$1", $cats);
                $cats = preg_replace('#<a([^>]+)>([^<]+)<\/a>#', $link_before . '<a$1' . $link_attr .'>' . $link_in_before . '$2' . $link_in_after .'</a>' . $link_after, $cats);
                if ($show_home_link) echo $sep;
                echo $cats;
            }
            if ( get_query_var('paged') ) {
                $cat = $cat->cat_ID;
                echo $sep . sprintf($link, get_category_link($cat), get_cat_name($cat)) . $sep . $before . sprintf($text['page'], get_query_var('paged')) . $after;
            } else {
                if ($show_current) echo $sep . $before . sprintf($text['category'], single_cat_title('', false)) . $after;
            }
        } elseif ( is_search() ) {
            if (have_posts()) {
                if ($show_home_link && $show_current) echo $sep;
                if ($show_current) echo $before . sprintf($text['search'], get_search_query()) . $after;
            } else {
                if ($show_home_link) echo $sep;
                echo $before . sprintf($text['search'], get_search_query()) . $after;
            }
        } elseif ( is_day() ) {
            if ($show_home_link) echo $sep;
            echo sprintf($link, get_year_link(get_the_time('Y')), get_the_time('Y')) . $sep;
            echo sprintf($link, get_month_link(get_the_time('Y'), get_the_time('m')), get_the_time('F'));
            if ($show_current) echo $sep . $before . get_the_time('d') . $after;
        } elseif ( is_month() ) {
            if ($show_home_link) echo $sep;
            echo sprintf($link, get_year_link(get_the_time('Y')), get_the_time('Y'));
            if ($show_current) echo $sep . $before . get_the_time('F') . $after;
        } elseif ( is_year() ) {
            if ($show_home_link && $show_current) echo $sep;
            if ($show_current) echo $before . get_the_time('Y') . $after;
        } elseif ( is_single() && !is_attachment() ) {
            if ($show_home_link) echo $sep;
            if ( get_post_type() != 'post' ) {
                $post_type = get_post_type_object(get_post_type());
                $slug = $post_type->rewrite;
                printf($link, $home_url . $slug['slug'] . '/', $post_type->labels->singular_name);
                if ($show_current) echo $sep . $before . get_the_title() . $after;
            } else {
                $cat = get_the_category(); $cat = $cat[0];
                $cats = get_category_parents($cat, TRUE, $sep);
                if (!$show_current || get_query_var('cpage')) $cats = preg_replace("#^(.+)$sep$#", "$1", $cats);
                $cats = preg_replace('#<a([^>]+)>([^<]+)<\/a>#', $link_before . '<a$1' . $link_attr .'>' . $link_in_before . '$2' . $link_in_after .'</a>' . $link_after, $cats);
                echo $cats;
                if ( get_query_var('cpage') ) {
                    echo $sep . sprintf($link, get_permalink(), get_the_title()) . $sep . $before . sprintf($text['cpage'], get_query_var('cpage')) . $after;
                } else {
                    if ($show_current) echo $before . get_the_title() . $after;
                }
            }
        // custom post type
        } elseif ( !is_single() && !is_page() && get_post_type() != 'post' && !is_404() ) {
            $post_type = get_post_type_object(get_post_type());
            if ( get_query_var('paged') ) {
                echo $sep . sprintf($link, get_post_type_archive_link($post_type->name), $post_type->label) . $sep . $before . sprintf($text['page'], get_query_var('paged')) . $after;
            } else {
                if ($show_current) echo $sep . $before . $post_type->label . $after;
            }
        } elseif ( is_attachment() ) {
            if ($show_home_link) echo $sep;
            $parent = get_post($parent_id);
            $cat = get_the_category($parent->ID); $cat = $cat[0];
            if ($cat) {
                $cats = get_category_parents($cat, TRUE, $sep);
                $cats = preg_replace('#<a([^>]+)>([^<]+)<\/a>#', $link_before . '<a$1' . $link_attr .'>' . $link_in_before . '$2' . $link_in_after .'</a>' . $link_after, $cats);
                echo $cats;
            }
            printf($link, get_permalink($parent), $parent->post_title);
            if ($show_current) echo $sep . $before . get_the_title() . $after;
        } elseif ( is_page() && !$parent_id ) {
            if ($show_current) echo $sep . $before . get_the_title() . $after;
        } elseif ( is_page() && $parent_id ) {
            if ($show_home_link) echo $sep;
            if ($parent_id != $frontpage_id) {
                $breadcrumbs = array();
                while ($parent_id) {
                    $page = get_page($parent_id);
                    if ($parent_id != $frontpage_id) {
                        $breadcrumbs[] = sprintf($link, get_permalink($page->ID), get_the_title($page->ID));
                    }
                    $parent_id = $page->post_parent;
                }
                $breadcrumbs = array_reverse($breadcrumbs);
                for ($i = 0; $i < count($breadcrumbs); $i++) {
                    echo $breadcrumbs[$i];
                    if ($i != count($breadcrumbs)-1) echo $sep;
                }
            }
            if ($show_current) echo $sep . $before . get_the_title() . $after;
        } elseif ( is_tag() ) {
            if ( get_query_var('paged') ) {
                $tag_id = get_queried_object_id();
                $tag = get_tag($tag_id);
                echo $sep . sprintf($link, get_tag_link($tag_id), $tag->name) . $sep . $before . sprintf($text['page'], get_query_var('paged')) . $after;
            } else {
                if ($show_current) echo $sep . $before . sprintf($text['tag'], single_tag_title('', false)) . $after;
            }
        } elseif ( is_404() ) {
            if ($show_home_link && $show_current) echo $sep;
            if ($show_current) echo $before . $text['404'] . $after;
        } elseif ( has_post_format() && !is_singular() ) {
            if ($show_home_link) echo $sep;
            echo get_post_format_string( get_post_format() );
        }
        echo $wrap_after;
    }
}

同时,我也把这个很棒的功能引入到 dangopress 主题 里面了,有兴趣的同学可以下载下来尝试下效果,这个主题接下来我还会继续维护。

我的网站效果:

面包屑导航效果

-- END --

WordPress 文章内嵌 Gist 代码

WordPress 内嵌 Gist 链接的方法很简单,将以下代码添加到当前主题的 functions.php 文件中:

/*
 * Embed gists with a URL in post article
 */
function dangopress_embed_gist($matches, $attr, $url, $rawattr)
{
    $embed = sprintf(
        '<script src="https://gist.github.com/%1$s.js%2$s"></script>',
        esc_attr($matches[1]),
        esc_attr($matches[2])
    );

    return apply_filters('dangopress_embed_gist', $embed, $matches, $attr, $url, $rawattr);
}
wp_embed_register_handler('gist', '#https?://gist\.github\.com(?:/[a-z0-9-]+)?/([a-z0-9]+)(\?file=.*)?#i', 'dangopress_embed_gist');

在上面的代码中,我们注册了 Gist 链接的处理方法 dangopress_embed_gist。当我们拷贝 Gist 链接到编辑框时,会调用改方法生成内嵌内容。

Gist 链接是通过注册过程中,指定的正则表达式匹配的:

#https?://gist\.github\.com(?:/[a-z0-9-]+)?/([a-z0-9]+)(\?file=.*)?#i

它可以匹配下面的任意一种形式:

https://gist.github.com/dangoakachan/443ca6efa9622deb3131   # a full gist url example
https://gist.github.com/443ca6efa9622deb3131                # but user name is optional

# If the gist contains multiple file, use "?file=youfile" to embed only one 
https://gist.github.com/e59891e80652bb209f8e?file=moderate.list  # embed moderate.list only

查看全文

WordPress 阻挡垃圾评论

我的博客人气一般,但是垃圾评论却特别之多,严重影响我的心情。自从开博客以来,我一直都是通过 Akismet 插件来发现和过滤垃圾评论,效果非常不错,很少有漏网之鱼。但是前两天在阅读了云淡然同学写的文章wp_create_nonce实现wordpress垃圾评论终极防御之后,我突然萌生出自己写一些阻挡垃圾评论策略的想法。

wordpress-spam

下面我介绍几条本博客现在正在使用的阻挡垃圾评论的策略,几天下来效果还可以接受。每一种策略都不是完美的,所以只能多种方式配合使用。当然,道高一尺,魔高一丈,很多策略都必须要不断地改进与完善,并且最好根据自己的情况适当地调整。当然,如果你不想折腾,还是老老实实地使用 Akismet 插件来防护吧,插件的功能更加完善可靠,本文的方法只适合于不想启用过多插件和喜欢折腾的同学。

.htaccess 文件

首先我们可以借助 .htaccess 文件来阻挡部分恶意的垃圾评论,这一类评论往往是通过脚本或者工具自动提交的。请将以下内容添加到网站根目录的 .htaccess 文件中:

RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} .wp-comments-post\.php$
RewriteCond %{HTTP_REFERER} !.*kodango.com.* [OR]
RewriteCond %{HTTP_USER_AGENT} ^$
RewriteRule (.*) http://%{REMOTE_ADDR}/$ [R=301,L]

上面的几条规则,可以阻挡来源不是你博客,或者 user agent 信息为空的机器人评论。

查看全文

WordPress 置顶文章推荐

新浪微博的置顶功能相信大家非常熟悉,其实 WordPress 早在 2.7 之后就拥有了文章置顶的功能。但是,实际上这个功能在大多主题上却没太多用武之地。我想其中的一个原因,是很多像我们这样的个人博客,首页基本上只展示最新的5-10篇文章,如果再置顶个几篇,首页的内容基本上就不变了。

不过,如果我们换个角度,把置顶文章从页面中央移到侧栏,这样的 效果应该会不错,首先首页的文章列表中不会受到置顶的干扰,同时位于侧栏的置顶文章又有类似文章推荐的效果,我们动手来试试。

整个解决方案需要考虑到两点:

  1. 首页展示文章的时候要忽略置顶文章,这里的忽略是指不置顶显示;
  2. 侧栏需要增加显示置顶文章列表的小工具;

步骤一:首页忽略置顶文章

在主题的 functions.php 中加入以下代码:

/*
 * Alter the main loop
 */
function dangopress_alter_main_loop($query)
{
    /* Only for main loop in home page */
    if (!$query->is_home() || !$query->is_main_query())
        return;

    // ignore sticky posts, don't show them in the start
    $query->set('ignore_sticky_posts', 1);
}
add_action('pre_get_posts', 'dangopress_alter_main_loop'); 

这里通过指定ignore_sticky_posts为1,查询出的文章列表就会忽略置顶文章。

查看全文

WordPress 访客评论显示欢迎信息

最近逛了不少的博客,发现有一个共通的小细节:当你再次访问时,在评论框上方会显示类似“欢迎回来,xxx..”的信息。当时觉得这个想法挺不错的,一定程度上提高了用户体验,因为访客短期内再次访问时,不需要重复输入个人的信息。

关于这个话题,Neoease 有专门写过一篇文章来介绍如何实现 —— 提高 WordPress 访客评论时的用户体验。这次我在制作 dangopress 时,也顺手加上了这点。效果参考下图:

关键问题:获取访客信息

花点时间去研究,其实整个实现过程并不复杂。这里的关键点是,如何判断访客已经在近期发表过评论。

查看全文

WordPress 分开显示评论与 Pingback

WordPress 中的留言有三种类型,分别是普通评论、Pingback 和 Traceback。Pingback 和 Trackback 都是别人对你文章的引用,两者有一些区别,但是实际上差不多。就我博客上的评论来说 Pingback 类型的居多,因为这种引用是自动完成的。

当你在文章中引用另外一篇文章的地址时,Wordpress 就会给对应的网站发送Pingback,当这些网站收到回复时,就会将 Pingback 的信息显示在评论中。关于这 Pingback 和 Trackback 的更多对比,可以阅读下这篇文章 WordPress中的Ping、Trackback与Pingback:概念、区别与运用

当然,最终是否显示 Pingback/Trackback 类型的评论完全由主题自行决定。有些主题选择干脆只显示普通类型的评论,而有些主题显示全部评论。dangopress 主题选择了后者,这也是大多数主题的方式。但是如果将这几种类型的评论混杂在一起,难免显得有点混乱,所以在这里本文介绍一种将普通评论与 Pingback/Trackback 区分显示的方法,这个方法参考自 WordPress 2.7 Trackbacks 的两种处理方法 - NeoEase

除了做基本的区分之外,我们还希望能够分别显示普通评论与 Pingback/Trackback 的数目。最终的显示效果参考本博客:

查看全文

Yoast Breadcrumbs 文章页面删除标题

Yoast Breadcrumbs是一款很出色的面包屑插件,它可以在文章、分类、归档、搜索等页面显示导航,SEO 的效果也非常好。

装上该插件后,搜索引擎也有很好的理解和展示, 下面是我上一篇文章的 Google 搜索结果:

Snip20130928_3

我博客上安装的是作者的另外一款很有名的插件 WordPress SEO by Yoast,默认集成了Yoast Breadcrumbs的功能。

但是,该插件有一个不足的地方是,在文章页面中,仍然会在导航栏上显示标题,这样标题就在两处地方重复显示了,那么如何解决这个问题,下面我们介绍两种方法。

方法一

在 functions.php 文件中增加以下代码(来自 How to remove page title from breadcrumb):

< ?php 
if (function_exists('yoast_breadcrumb')) { 
    $opt = get_option("wpseo_internallinks"); 
    $sep = ( isset($opt['breadcrumbs-sep']) && $opt['breadcrumbs-sep'] != '' ) ? $opt['breadcrumbs-sep'] : '»'; 
    $breadcrumbs = yoast_breadcrumb('', '', false); 
    $linksArr = split($sep, $breadcrumbs); 
    array_pop($linksArr); 
    echo '<nav id="breadcrumbs">'.implode($sep,$linksArr).''; 
}
?>

查看全文