Jex’s Note

前端 HTML/CSS/JS

SEO

搜尋引擎將在子網域的網站視為完全不同的站

而將子目錄視為一個已存在的網站組成的一部份

加上 rich snippet

這是一個提供額外的資訊當在搜尋引擎列出結果時, 然候你要列出來的資訊有很多型態, 可能是你的產品, 額外想要顯示的售價等等

型態被定義在 schema.org, 你必須先確定你想顯示的資料型態為何, 再按照這個型態所定義的標籤屬性

例如 Product, 格式就要參考 schema.org/Product, code 請參考 Google 範例

如何加上? 在 body 或 header 任意地方加上 schema 規定的屬性 即可, 可以利用 google - Structured Data Testing Tool 幫忙檢查你要加上的是否被正確分析 :

<div itemscope itemtype="http://schema.org/Book">
    <span itemprop="name"> Inbound Marketing and SEO: Insights from the Moz Blog</span>
    <span itemprop="author">Rand Fishkin</span>
</div>

可參考 此站

覆寫網站所有字型

html * {
    font-family: 'Arial Black', 'Arial', "新細明體", 'PMingLiU', 'sans-serif';
}
  • PMingLiU : 新細明體的英文版名稱
  • sans-serif : 無襯線字

base64 圖檔

<img src="data:image/jpeg;base64,/9j/4AAQSknK2h//2Q==" alt="" />

Preview image and get actual width and height

HTML :

<input type="file" id="add-photo" name="add_photo">
<img width="200" height="100" id="add-photo_preview"/>

JS :

$('#add-photo').on('change', function (e) {
    // Create img
    var tempImg = document.createElement('img');
    console.log(document.querySelector('#add-photo').files[0]);  // File object
    // Put local image object into tempImg
    tempImg.src = window.URL.createObjectURL(document.querySelector('#add-photo').files[0]);
    tempImg.onload = function() {
        // Render image
        window.URL.revokeObjectURL(this.src);
        // We can get actual width and height.
        console.log(this.width);
        console.log(this.height);
    };
    $("#add-photo_preview").attr('src', tempImg.src);
});

Get checkbox array

HTML :

<input type="checkbox" class="courses" data-course-id="1" value="1">
<input type="checkbox" class="courses" data-course-id="2" value="2">

JS :

var courses = [];
$(".courses:checked").each(function() {
    courses.push($(this).data('course-id'));
});

Get radio value

HTML :

<input type="radio" name="role" id="role-student" value="student" checked>
<input type="radio" name="role" id="role-parent" value="parent">

JS :

role = $('input[name=role]:checked').val();

name 是陣列的話要加上引號
$('input[name="user[domain]"]:checked').length  // 沒選擇為 0, 選擇其中一項為 1

判斷 group radio 其中一項有沒有被選取

$('input:radio[name=language_preference]').is(":checked")

只返回 true 或 false,

uncheck radio/checkbox group

radio :

$('input[name=sex]').each(function () {
    $(this).prop('checked', false);
});

checkbox :

$("input[name='exercises[]']").each(function () {
    $(this).prop('checked', false);
});

Select 初始完後直接觸發

$('#exercises').on('change', funciton () {
    // do something
}).trigger('change');

Javascript tirgger HTML5 native validation

HTML:

<form id="test_form">
    <input type="text" name="location" required/>
</form>

JS:

$('#test_form').on('click', function () {
    if (test_form.checkValidity()) {
        console.log('ok');
    } else {
        console.log('fail');
    }
});

捲軸移動超過 content, sidebard 跟著移動

HTML:

<div id="header">Header</div>
<div id="wrapper">
    <div id="left">
        <div id="sidebar">Sidebar Text here!</div>
    </div>
    <div id="right">This is the text of the main part of the page.</div>
    <div class="clear"></div>
</div>
<div id="footer">Footer</div>

CSS:

#header {
    background: #c2c2c2;
    height: 50px;
}
#wrapper {
    position: relative;
    min-height: 500px; /* Just as an example */
    width: 500px;
}
#left {
    position: absolute;
    background: #d7d7d7;
    width: 150px;
    height: 100%;
}
#right {
    position: relative;
    width: 350px;
    float: right;
}
#sidebar {
    background: #0096d7;
    width: 150px;
    color: #fff;
}
.clear {
    clear: both;
}
#footer {
    background: #c2c2c2;
    height: 500px; /* Just as an example */
}

JS:

$(document).ready(function () {
    var length = $('#left').height() - $('#sidebar').height() + $('#left').offset().top;
    $(window).scroll(function () {
        var scroll = $(this).scrollTop();
        var height = $('#sidebar').height() + 'px';
        if (scroll < $('#left').offset().top) {
            $('#sidebar').css({
                'position': 'absolute',
                'top': '0'
            });
        } else if (scroll > length) {
            $('#sidebar').css({
                'position': 'absolute',
                'bottom': '0',
                'top': 'auto'
            });
        } else {
            $('#sidebar').css({
                'position': 'fixed',
                'top': '0',
                'height': height
            });
        }
    });
});

或直接用套件 Sticky-Kit, 用法很簡單

記得在 wrapper 底下做 clear: both, 否則 sidebar 太長會穿過 wrapper

ref: FIXING A SIDEBAR WHILE SCROLLING, UNTIL BOTTOM WITH JQUERY

HTML5 Sortable

HTML:

<script type="text/javascript" src="js/jquery.sortable.min.js"></script>
未分班學生 :
<ul class="name_container">
</ul>
己分班學生
<ul class="name_container">
</ul>

JS:

// 學生名字標簽可拖曳
$('.name_container').sortable({
    connectWith: '.name_container'
});

CS:

/* 所有名稱標簽的長寬 */
.name_container {
    min-height: 100px;
    width: 200px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    list-style-type: none;
    padding: 0px;
    margin: 0px;
}
.name_container li {
    border: 1px solid #CCC;
    background: #F6F6F6;
    font-family: "Tahoma";
    color: #1C94C4;
    margin: 2px;
    height: 30px;
    line-height: 30px;
    overflow: hidden;
    white-space: nowrap;
    text-align: center;
}
.name_container li.highlight {
    background: #FEE25F;
}

/* 拖移時顯示的預放位置以虛線顯示 */
li.sortable-placeholder {
    border: 1px dashed #CCC;
    background: none;
}

overflow-x: scroll 水平排列

HTML:

<div class="container">
    <div class="item">item1</div>
    <div class="item">item2</div>
    <div class="item">item3</div>
    <div class="item">item4</div>
</div>

CSS:

.container{
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
}
.item {
    vertical-align: top;
    width: 300px;
    display: inline-block;
}

audio speed control

JS :

function initAudio(){
    var audio = new Audio();
    audio.src = "http://downloads.bbc.co.uk/learningenglish/features/6min/151105_6min_english_plastic_bags_download.mp3";
    audio.play();
    var speedlist = document.getElementById("speedlist");
    speedlist.addEventListener("change",changeSpeed);
    function changeSpeed(event){
        audio.playbackRate = event.target.value;
    }
}
window.addEventListener("load", initAudio);

HTML :

<select id="speedlist">
  <option value="1">change speed</option>
  <option value=".5">.5</option>
  <option value="1">Normal</option>
  <option value="1.5">1.5</option>
  <option value="2">2</option>
</select>

判斷滑鼠 mouse event which

$('#mylink').click(function(e){
    if ( (e.which == 1) ) {
        alert("left button");
    } else if ( (e.which == 2) ) {
        alert("middle button");
    } else if ( (e.which == 3) ) {
        alert("right button");
    }
});

失效則使用 e.preventDefault()

js l10n

util.js :

(function () {
    var L10N = window.L10N || {};
    window.L10N = L10N;

    if (!L10N.util) {
        L10N.util = {};

        L10N.util.getTrans = function (key, token) {
            if (typeof L10N.lang[key] === 'undefined') {
                return 'undefine-string!!';
            }
            var value = L10N.lang[key];
            if (typeof token === 'object') {
                for (var i in token) {
                    value = value.replace('{' + i + '}', token[i]);
                }
            }
            return value;
        };

        L10N.util.htmlspecialchars = function (str) {
            return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
        };
        L10N.util.htmlspecialchars_decode = function (str) {
            return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"");
        };
        L10N.util.nl2br = function (str) {
            return str.replace(/\n/g, "<br>");
        };
        L10N.util.trimDot = function (value) {
            return value.replace(/[.\s]+$/, '');
        };
    }

})();

lang/zh-TW/l10n.js :

(function () {
    var L10N = window.L10N || {};
    L10N.lang = L10N.lang || {};
    // common
    L10N.lang['admin-common-system_info'] = '系统讯息';

    // system - group
    L10N.lang['admin-system-group-edit_success'] = '修改成功';
})();

test/main.js :

alert(L10N.util.getTrans('admin-common-system_info'));

一些觀念

  • AngularsJS 與 bootstrap 一起使用的話要把 angular 的 $ 換掉
  • Angular 建議用第 2 版, 聽說改善很多效能問題
  • iOS 可用 react native, android 的 react native 要再等等
  • ios react native = javscript + swift
  • react native 是 native app 不是 web app

jQuery validate

安裝

下載頁面, download 載下來是一大包, 只需要 dist/jquery.validate.min.js 就好

或 Rails gem

gem 'jquery-validation-rails', '~> 1.13.1'
bundle install

assets/javascripts/application.js :

//= require jquery.validate
//= require jquery.validate.localization/messages_zh_TW
//# require jquery.validate.additional-methods

Rails 記得重啟才會生效

Example

$("#my-form").validate({

  rules: {
    'user[first]': {
      required: true
    }
  },

  // Specify the validation error messages
  messages: {
    'user[first]' : {
      required: 'First 未填'
    }
  },

  submitHandler: function(form) {
    form.submit();
  }
});

<button type="submit" class="btn btn-success">submit</button>

送出後 validate 會自動攔截 submit 行為, 通過後才會送出

validate

required : true
equalTo : "#confirmed_password"
rangelength : [2, 20]
minlength: 8

custom validation :

$.validator.addMethod(
  "alphabets", function(value, element, regexpr) {
    return regexpr.test(value);
  },"請輸入英文字母");

alphabets : /^[A-Za-z]{2,20}$/

預設是無法 Validate Hidden field 的, 加上 ignore: "" 就可以了

$("#form1").validate({
    ignore: "",
    rules: {
        something: {
            number:true,
            min:1,
            required:true
        }
    }
});

驗證 file 方法1

$('#my-form').validate({
    rules: {},
    messages: {},
    ...
});
$('input[name^="fileupload"]').rules('add', {
    required: true,
    accept: "image/jpeg, image/pjpeg"
})

驗證 file 方法2

rules: {
  'user[doc_file]': {
    required: true
  }
},
messages: {
  'user[doc_file]': {
    required: '請選擇檔案'
  }
},

messages

不管是自訂的 validation 還是預設的, 錯誤訊息 name 都是對應的

覆寫預設的錯誤訊息樣式

$.validator.setDefaults({
    errorElement: "span",
    errorClass: "help-block",
    highlight: function (element, errorClass, validClass) {
        $(element).closest('.form-group').addClass('has-error');
    },
    unhighlight: function (element, errorClass, validClass) {
        $(element).closest('.form-group').removeClass('has-error');
    },
    errorPlacement: function (error, element) {
        if (element.parent('.input-group').length || element.prop('type') === 'checkbox' || element.prop('type') === 'radio') {
            error.insertAfter(element.parent());
        } else {
            error.insertAfter(element);
        }
    }
});

判斷手機瀏覽器

function isMobile(){
    return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk/i.test(navigator.userAgent||navigator.vendor||window.opera)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test((navigator.userAgent||navigator.vendor||window.opera).substr(0,4)))
}

if(isMobile()) {
  alert('建議您在桌機瀏覽以獲得最佳顯示效果');
}

RWD

手機瀏覽

css 沒有直接判斷是否是手機瀏覽的函式可用, 它是判斷它的寬度, 當它寬度大於多少或小於多少, 先看範例

// This applies from 0px to 600px
body {
  background: red;
}

// This applies from 992px onwards       一般來說 bootstrap 的樣式在 992 以下就會跑掉了
@media (min-width: 992px) {
  body {
    background: green;
  }
}

有張背景圖, 如果是手機瀏覽就用比較小的那一張, 可以這樣寫 :

#banner-back-image {
    background-image:image-url('application/back_original.jpg');
    @media (max-width: 500px) {
        background-image:image-url('application/back_mobile.jpg');
    }
    background-repeat: no-repeat;
    background-size: cover;
    background-position:center center;
    padding: 50px 0; /* fix header position */
}

一般手機寬度差不多 400 左右, 所以我在這判斷小於 500 就當它是用手機瀏覽

chrome 瀏覽器上的 UI 顏色

<meta name="theme-color" content="#db5945">

斷行

.break-word {
    word-wrap: break-word;
    word-break: break-all;
}

另開新頁

唯一要注意的是如果開其他網站的話 chrome 會把它擋下來, 最好是自已網站的頁面

window.open('url...', '_blank');

fix anchor 定位偏移的問題

假設使用 bootstrap 時設定 body 出現的位置時會給 padding :

body {
     padding-top: 50px;
}

它會造成 anchor 的位置錯誤

解決方法是加上一個修正 anchor 位置的 css, 並在 anchor 上加上這個 class

.fix-anchor-position {
    padding-top: 50px;
    margin-top: -50px;
}

<div id="info" class="fix-anchor-position"> ... </div>

GA

引入 GA + [In-Page Analytics] Enable enhanced link attribution in the reports

In-Page Analytics 是用來分析網頁上按鈕各被點了幾次

1) 加上

ga('create', 'UA-XXXX-X');
ga('require', 'linkid');    <= 記得加上它
ga('send', 'pageview');

2) 設定

Admin -> Property Settings ->  Use enhanced link attribution -> Apply
Admin -> View Settings -> Website's URL   要選擇對的 protocal, 很重要, 如果網站是 https 這邊設定到 http In-Page 會一直出錯

3) 載 In-Page Analytics 的 chrome extension, 並且在 URL bar 後面有個盾, 點一下, 讓它執行 insecure code

4) 在後台 Behaviour -> In-Page Analytics 就可以看到了, 它會 iframe 你的網頁, 並且在按鈕上顯示被點擊多少次

用新版 https://www.google.com/analytics, 不要用舊版 https://analytics.google.com/analytics, 否則可能會跑不出來

[In-Page Analytics] GA 後台顯示錯誤

Problem loading In-Page Analytics
We've identified problems in your setup. These may cause problems loading In-Page Analytics.
Your site is configured to set X-Frame-Options: headers. In-Page Analytics can only work in Full View mode on your site.
You can try the Page Analytics Chrome Extension which has almost identical functionality to the In-Page Analytics report, but can often resolve these issues.

然候我看到我的網站有這個 Header

X-Frame-Options:SAMEORIGIN

browsers use this header to decide whether or not your site can be iframed by other sites.

這是因為 Rails 4 為了安全性預設加上這個 header

解決方案是把這個 header 刪除, 或 value 改成 ALLOWALL, ALLOW-FROM http://example.com

在 config/application.rb

config.action_dispatch.default_headers = {
  'X-Frame-Options' => 'ALLOW-FROM https://google.com',
}

如果 GA 的 Embedded mode 還是出不來看看開發者工具 console 有沒有噴錯

Failed to execute ‘postMessage’ on ‘DOMWindow’ (未解決)

開發者工具顯示的 error

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://www.google.com') does not match the recipient window's origin ('https://analytics.google.com').            inpage.js:225

有 google 到解法,手動將 analytics.google.com 改變 sub-domain 為 www.google.com 就可以了,但我會一直被 redirect 回 analytics.google.com

Track button click

它可以用來偵測特定的按扭被點選幾次

1) 先到 GA 後台設定 Event

Category : button (自已取)
Action : click (自已取)
Label : contact_translator (自已取)

HTML :

<button type="button" onclick="ga('send', 'event', 'button', 'click', 'contact_translator');"/>

當按下後,就可以馬上在 GA 後台 realtime 那裡看到結果了

其他

讓某塊這個變暗淡

.opacity-04 { opacity: 0.4; }

算出正確字數

function newline_char_count(text) {
  var newLines = text.match(/(\r\n|\n|\r)/g);
  var addition = 0;
  if (newLines != null) {
      addition = newLines.length;
  }
  return addition;
}

$('#feedback').keyup(function () {
  $('#feedback-count').html($(this).val().length + newline_char_count($(this).val()));
});

JavaScript 畫一個愛心 (用 chrome console)

s="";for(k=800;k--;)
x=1.25-k%40/16,y=k/320-1.25,
s+=Math.pow(x*x+y*y-1,3)<x*x*y*y*y
?"Love"[k%4]:39==k%40?"\n":" ";s

Bootstrap

版面

本身左右會留一些空間

<div class="container">
  ...
</div>

全版, full width container, spanning the entire width of your viewport

<div class="container-fluid">
  ...
</div>

引入時, 不要把 row 寫在 partial 裡

partial

<div class="col-md-12">
    ...
</div>

否則即使用 container-flud 包起來也可能發生版面超過

<div class="container-fluid">
  <div class="margin-top-50">
    <%= render ... partial view ... %>
  </div>
</div>

Modal

<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
  Launch demo modal
</button>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header bg-primary">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="myModalLabel">Modal title</h4>
      </div>
      <div class="modal-body">
        ...
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

Panel

<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">Panel title</h3>
    </div>
    <div class="panel-body">
        Panel content
    </div>
</div>

popover

基本用法

HTML :

<span class="badge popover-hint" data-content="獲得評價次數">15</span>

JS :

$('.popover-hint').popover({
  trigger: 'hover',
  placement: 'auto',                // hint 出現的位置
  container: 'body'                 // 讓 popover 調整為適合內文的寬度
});

Custom

HTML:

// 讓取消事件可以取到 test-id, 進而觸發 close
<a href=\"javascript: void(0);\" class=\"close\" data-test-id=\"{$row['id']}\">&times;</a>

// test-id 為了可以 close
<button id='test-{$row['id']}' class='btn btn-success btn-xs' title='Demo popover' data-toggle='popover'>See more</button>
<div class='popover-content hide'>$content</div>

// content 不一定要用 js 產生, 可以直接寫在 html 裡
<li data-toggle='popover' title='Demo title' data-content='Demo content' data-html='true'>

CSS:

.popover {
    z-index: 9999;      // 避免被排在其他的下面
    position: fixed;   //避免被 限制大小的 diev 切掉
    font-size: 12px;
    line-height: 16px;
}

click + close 事件 :

// 基本資料及更多資料
$('[data-toggle="popover"]').popover({
    html: true,
    trigger: 'click',           // or 'hover'
    placement: 'top',
    content: function () {
        return $(this).next('.popover-content').html();
    }
}).on('shown.bs.popover', function(e) {
    var $pop_content = $(this).next();
    $pop_content.find('.close').click(function(){
        $('#test-' + $(this).data('test-id')).click();      // 再點一下就會關閉, 所以再觸發一次
    });
});

hover :

$('[data-toggle="popover"]').popover({
    html: true,
    trigger: 'hover',
    placement: 'auto',
    content: function () {
        return $(this).next('.popover-content').html();
    }
});

hover 到 popup 不要消失 :

$('[data-toggle="popover"]').popover({
    html: true,
    trigger: 'manual',
    placement: 'right',
    content: function () {
        return $(this).next('.popover-content').html();
    }
}).on("mouseenter", function () {
    var _this = this;
    $(this).popover("show");
    $(".popover").on("mouseleave", function () {
        $(_this).popover('hide');
    });
}).on("mouseleave", function () {
    var _this = this;
    setTimeout(function () {
        if (!$(".popover:hover").length) {
            $(_this).popover("hide");
        }
    }, 300);
});

Datepicker

安裝

gem 'bootstrap-datepicker-rails', '~> 1.4.0'

application.scss

*= require bootstrap-datepicker3
@import "bootstrap-sprockets";
@import "bootstrap";

application.js

Load 所有 locales
//= require bootstrap-datepicker

或單獨引入主程式及要的語言
//= require bootstrap-datepicker/core
//= require bootstrap-datepicker/locales/bootstrap-datepicker.zh-TW.js

bundle install 完記得重啟 rails

Example

HTML :

<input class="form-control" type="text" id="date-from">

JS :

$('#date-from').datepicker({
  format: 'yyyy/mm/dd'
});

Switch

Install

gem "bootstrap-switch-rails", '~> 3.3.3'

application.scss

*= require bootstrap3-switch
@import "bootstrap-sprockets";
@import "bootstrap";

application.js

//= require bootstrap-switch

bundle install 完記得重啟 rails

Example

HTML :

<input type="checkbox" id="switch" <%= @enabled %>>

JS :

$.fn.bootstrapSwitch.defaults.size = 'mini';
$('#switch').bootstrapSwitch();
$('#switch').on('switchChange.bootstrapSwitch', function (event, state) {
    console.log(this); // DOM element
    console.log(event); // jQuery event
    console.log(state); // true | false
});

file upload

HTML :

<span id="file-upload" class="btn btn-default btn-file">
  選擇檔案 <%= f.file_field :original_file %>
</span>
<span id="file-name"><span>

CSS :

.btn-file {
    position: relative;
    overflow: hidden;
}
.btn-file input[type=file] {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 100%;
    min-height: 100%;
    font-size: 100px;
    text-align: right;
    filter: alpha(opacity=0);
    opacity: 0;
    outline: none;
    background: white;
    cursor: inherit;
    display: block;
}

JS :

// 上傳檔案 - 從 path 中取得檔名
$(document).on('change', '.btn-file :file', function() {
    var input = $(this),
        numFiles = input.get(0).files ? input.get(0).files.length : 1,
        label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
    input.trigger('fileselect', [numFiles, label]);
});

// 上傳檔案 - 將檔名顯示出來
$('#file-upload :file').on('fileselect', function(event, numFiles, label) {
  console.log(numFiles);
  console.log(label);
  $('#file-name').html(label);
});

dropdown

<div class="dropdown">
  <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
    Dropdown
    <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>

好用的第三方

dropdown

<select class="selectpicker show-tick">
  <option>item1</option>
  <option>item2</option>
  <option>item3</option>
</select>

$('.selectpicker').selectpicker();

datepicker

<input type="text" class="datepicker"/>

$('.datepicker').datepicker({
  format: 'yyyy-mm-dd',
  autoclose: true
});

ref

Install Ruby on Rails

(最後更新: 2016-04-14)

Install Rails

[1] Update & Upgrade

sudo apt-get update
sudo apt-get upgrade -y

[2] Install RVM (Ruby Version Manager)

Install curl (MacOS doesn’t need to execute this)

sudo apt-get install curl

Install RVM

\curl -sSL https://get.rvm.io | bash -s stable

讀取 rvm

source ~/.rvm/scripts/rvm

安裝 rvm 自己相依的東西

rvm requirements

[3] Install Ruby

rvm install ruby
rvm use ruby --default

[4] Install RubyGems

rvm rubygems current

[5] Install rails

gem install rails

安裝 rails 的 doc 很久, 可以加上 -no-rdoc --no-ri 忽略 doc

[6] 安裝 node.js

sudo apt-get install nodejs

or mac

brew install node

[7] Test

cd /tmp
rails new rails_test
cd rails_test
rails s

預設 port 是 3000

Upgrade 己存在專案的 Rails 版本

安裝最新版本的 Rails

gem install rails

修改 Gemfile, 改成指定版本

gem 'rails', '4.2.3'

Update 專案引入的套件

bundle update

Deploy Rails

(最後更新: 2016-04-14)

[1] 安裝 Passenger + Nginx

安裝 passenger

sudo apt-get install libcurl4-openssl-dev       # passenger 需要
gem install passenger

安裝 nginx

rvmsudo passenger-install-nginx-module          # 用 passenger 安裝 nginx
  • 安裝在預設路徑 /opt/nginx, 千萬不要改, 免得 nginx 指令會無效
  • 如果有出現 export rvmsudo_secure_path=1 執行它
  • 記憶體太少可能會導致安裝失敗, 如果顯示要你增加 swap 可參考此篇

如果安裝 passenger-install-nginx-module 發生錯誤

/home/web-admin/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/rubygems/specification.rb:2158:in `method_missing': undefined method `this' for #<Gem::Specification:0xdf627c passenger-5.0.27> (NoMethodError)
from /home/web-admin/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/rubygems/specification.rb:1057:in `find_active_stub_by_path'
from /home/web-admin/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:64:in `require'
from /home/web-admin/.rvm/gems/ruby-2.3.0/gems/passenger-5.0.27/bin/passenger-install-nginx-module:33:in `<top (required)>'
from /home/web-admin/.rvm/gems/ruby-2.3.0/bin/passenger-install-nginx-module:23:in `load'
from /home/web-admin/.rvm/gems/ruby-2.3.0/bin/passenger-install-nginx-module:23:in `<main>'
from /home/web-admin/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `eval'
from /home/web-admin/.rvm/gems/ruby-2.3.0/bin/ruby_executable_hooks:15:in `<main>'

看起來可能是我預設用 ruby-2.3 發生問題,所以先降回 ruby-2.2

rvm install ruby-2.2.4
rvm use ruby-2.2 --default

再安裝一次就成功了!

[2] 安裝成功後設定根目錄

建立 /var/www 目錄

設定 /opt/nginx/conf/nginx.conf, 將 location / { 範圍內註解掉, 並在 http { 內加上 :

error_log /var/www/rails_app/log/nginx_error.log;
access_log /var/www/rails_app/log/nginx_access.log;
server {
    listen 80;
    server_name 106.185.47.26;  # or domain name
    root /var/www/rails_app/public;
    passenger_enabled on;
}
  • nginx 執行時的 user 可以不用特別指定, 預設是
  • 如果要跑 development 環境則加上 rails_env development;

[3] 安裝 nginx 指令

如果有開 tmux 記得關掉再執行 :

cd /tmp
wget -O init-deb.sh https://www.linode.com/docs/assets/660-init-deb.sh
sudo mv init-deb.sh /etc/init.d/nginx
sudo chmod +x /etc/init.d/nginx
sudo /usr/sbin/update-rc.d -f nginx defaults

start

sudo /etc/init.d/nginx start
or
sudo service nginx start

[4] 把 code 放到 /var/www

bundle install

如果顯示 bundle 沒安裝,執行:gem install bundle

[5] 環境

development :

rake db:migrate RAILS_ENV=development

production :

1) 產生 Key

rake secret             # 產生key

2) 設定 secret, 有兩種方式, 擇一就好

第一種

config/secrets.yml
    production:
      secret_key_base: cf2d4472039660a31a002b21cd3ded0cf7cc2c5a0d82f24dcdf5097b79c1900241f97eb85542f8e4a349f32fac37b618bc663b21f16de2706bb897885d6cc3f0

第二種

1) nginx.conf :
    passenger_env_var SECRET_KEY_BASE "cf2d4472039660a31a002b21cd3ded0cf7cc2c5a0d82f24dcdf5097b79c1900241f97eb85542f8e4a349f32fac37b618bc663b21f16de2706bb897885d6cc3f0";

2) config/secrets.yml
    production:
      secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

3) DB migrate + Assets precompile

First time :

RAILS_ENV=production rake db:create
RAILS_ENV=production rake db:migrate

Update code :

RAILS_ENV=production bundle exec rake assets:precompile

4) Restart web server

sudo service nginx restart

網頁不通

出現錯誤 500 We're sorry, but something went wrong.

1) 判斷是 nginx 的 500 還是 Rails 的 500 (看頁面的 html 及 css 可以判斷)

2) 500 的話先看, 目錄權限有沒有問題, www.example.com/robots.txt 讀取 public/robots.txt 看通不通

注意, 如果放在 /root 下, 因為 /root 的權限還是 root 的, 即使網站 foler 改成 www-data 也沒用, 建議放在 /var/www 下, /var/www 權限記得要給 www-data

nginx 預設 user 是 nobody, 建議改成 www-data, 並且確定網站根目錄的權限也是 www-data

3) 當 nginx 及 rails log 都沒有異樣, 執行 RAILS_ENV=production rails c 看有沒有錯誤, 如果有錯誤會導致 nginx 的 500

4) 檢查在 development 環境是否正常 rails s -b 0.0.0.0

5) 檢查 production 是否正常 RAILS_ENV=production rails s -b 0.0.0.0

確定有做 assets precompile, 如果 public/assets 有檔案但 404

environments/production.rb, 改成 true :

config.assets.compile = true

確定網頁都沒有 404 等等之類的問題

6) 再回去看 log/production.log 有沒有異常

很有可能會發生內建的 http server : WEBrick 執行的權限是夠的, 但 nginx 執行權限不夠導致錯誤

I18n 導致錯誤

I, [2015-08-22T06:53:22.272463 #7927]  INFO -- : Completed 500 Internal Server Error in 490ms (ActiveRecord: 0.0ms)
F, [2015-08-22T06:53:22.273759 #7927] FATAL -- :
I18n::InvalidLocaleData (can not load translations from /usr/local/rvm/gems/ruby-2.2.1/gems/devise-i18n-views-0.3.4/lib/../locales/pt-PT.yml: #<Errno::EACCES: Permission denied @ rb_sysopen - /usr/local/rvm/gems/ruby-2.2.1/gems/devise-i18n-views-0.3.4/lib/../locales/pt-PT.yml>):
  app/controllers/application_controller.rb:18:in `set_locale'

調整權限

chmod -R 777 /usr/local/rvm/gems/ruby-2.2.1/gems

沒有 permission 問題後, 網頁就能 work 了

403 Forbidden

以下有幾個方向可以找出問題在哪裡 :

  • 重啟 nginx 看看有沒有出現什麼異常的訊息,有可能 passenger_root 路徑設定錯導致找不到檔
  • 確定網站根目錄的權限是 nginx 可以執行的
  • 去 nginx 的 error log 看看有沒有線索

完整的 nginx.conf

  • 區分 dev (3000 port) / production 環境 / ssl
  • passenger_root : 可用 passenger-config --root 得知
  • passenger_ruby : 可用 passenger-config --ruby-command 得知

/opt/nginx/conf/nginx.conf :

user web-admin;
worker_processes  1;

events {
    worker_connections  1024;
}


http {
    passenger_root /usr/local/rvm/gems/ruby-2.2.1/gems/passenger-5.0.15;
    passenger_ruby /usr/local/rvm/gems/ruby-2.2.1/wrappers/ruby;

    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    server {
        listen 443 ssl;
        ssl_certificate /opt/nginx/ssl/example_combined.crt;
        ssl_certificate_key /opt/nginx/ssl/example.key;

        client_max_body_size       50M;

        listen       80;
        server_name  example.com;

        # non-www redirect to www
        if ($host = $server_name) {
            return 301 https://www.$server_name$request_uri;
        }

        # 將 http 導到 https
        if ($scheme = http) {
            return 301 https://www.$server_name$request_uri;
        }

        # 注意 owner 可能引發 500
        root /var/www/example/public;
        passenger_enabled on;
        rails_env production;

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    server {
        listen 3000;
        server_name dev.example.com;

        client_max_body_size       50M;

        # 注意 owner 可能引發 500
        root /var/www/example/public;
        passenger_enabled on;
        rails_env development;
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

其他

關閉 Development 環境的錯誤訊息

config/environments/development.rb

config.consider_all_requests_local       = false

重啟 Rails app

在網站根目錄下新增

touch tmp/restart.txt

Reload 頁面就會觸發重新啟動

Once Passenger has noticed that the file’s timestamp has changed, it will restart the application.

Mac

一般鍵盤接 Mac

  1. 系統偏好設定鍵盤
  2. TAG選鍵盤,再點下面的變更鍵
  3. 下拉選單選USB插上的鍵盤 → OPTIONOPTION

也就是兩個對調,ALT 就成了⌘ 鍵

RAR

安裝

brew install unrar

將 multi part rar files 解開

unrar x -e video.part1.rar

會自動將其它部份一起解開

iTerm

  • + d : 垂直 split windows
  • + shift + d : 水平 split windows
  • + t : 打開分頁
  • + shift + 左右鍵 : 移動 tab 位置
  • + 左右 : 切換 iterm2 的 tab
  • + shift + { or } : 切換 tab // 與上行功能一樣, 可刪?? 20140309
  • alt + 滑鼠左鍵 : 用滑鼠移動vim游標
  • + / : highlight 目前游標的位置

連接 samba (網路芳鄰)

finder -> 前往 -> 連接伺服器 -> 輸入 smb://192.168.1.236/samba

URL 後面的 samba 為主機定義

在 terminal 下使用 sublime 開啟檔案

ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime

finder

  • + shift + g : 輸入指定路徑並前往該資料夾

觸控板

  • 觸控板 - 4指向兩側擴散 : 回到桌面
  • 觸控板 - 4指向中間集中 : 回到工作的視窗

符號

  • : Shift + Alt + k
  • Ctrl + + Space : 表情符號

複製/貼上

檔案複製

  • + C : 複製
  • + V : 貼上 (原檔案還在)

檔案剪下

  • + C : 複製
  • + option + V : 貼上 (原檔案不在, 會移動到新的位置)

其他

  • + alt + d : 查詢字典
  • + A : 全選
  • + Z : 復原
  • + P : 播放英文語音
  • + W : 關閉視窗
  • + Q : 結束應用程式
  • + + : 放大頁面
  • + : 縮小頁面

系統

  • + Option + Esc : 叫出強制結束應用程式列表
  • + Tab : 切換視窗
  • + , : 叫出目前程式的偏好設定

編輯

  • + 方向鍵右鍵 : 將Cursor移至行末
  • + 方向鍵左鍵 : 將Cursor移至行首
  • + 方向鍵上鍵 : 跳到文件頂端
  • + 方向鍵下鍵 : 跳到文件底端

截圖

  • + shift + 3 : Screenshot
  • + shift + 4 : Screenshot crop
  • + shift + 4 + space : Screenshot 選定的視窗

Safari && Chrome && Firefox && iTerm

  • + T : 開新Tab
  • + W : 關閉Tab

清除 DNS Cache

# OSX 10.10 版本使用
sudo discoveryutil udnsflushcaches

更新 vim

# 如果沒有 mercurial 要先更新
brew install mercurial
brew install vim

如果安裝完後, 用 vim -v 判斷判本不是用最新的 vim 的話

可能是 $PATH/usr/bin/usr/local/bin 前面, 所以實際上執行還是抓到 mac 內建的 vim (/usr/bin/vim)

只要將原本的 /usr/bin/vim rename, 應該就能吃到 brew 安裝的 /usr/local/bin/vim

sudo mv /usr/bin/vim /usr/bin/vim7.3

Terminal call Finder

pop-up / alert

osascript -e 'tell app "System Events" to display dialog "Hello World"'

open finder

open .

目前連接 USB 的硬體有哪些

ioreg -p IOUSB -w0 | sed 's/[^o]*o //; s/@.*$//' | grep -v '^Root.*'
或
system_profiler SPUSBDataType

Change screenshot save location

defaults write com.apple.screencapture location /Users/jex/Desktop/Screenshots
killall SystemUIServer

Resolution

如果接上的螢幕沒有太多解析度選, 就按著 option 再選 Scaled 就會挑出隱藏版的解析度了

Kill 一直再生的 process

e.g. forticlient

sudo kill .......
launchctl list | grep forti
launchctl remove com.fortinet.credential_store

Linux Mount 掛載

mount 唯讀

mount -r /dev/sda1 /test

掛載 usb

sudo fdisk -l
cd /mnt
sudo mkdir usb
sudo mount -v -t ntfs /dev/sdb5 /mnt/usb

-v, –verbose : Verbose mode. -t, –types vfstype : The argument following the -t is used to indicate the filesystem type. -t 不一定要明確指定格式, 也可以讓系統自動判別, ex: -t auto

卸載 usb

sudo umount /dev/sdb5

Shell Script Examples

時間

DATE=`date +%Y-%m-%d`               // 2016-12-24
DATE=`date +%Y-%m-%d:%H:%M:%S`      // 2016-12-24:10:28:58

取得網卡 mac

pi@raspbmc:~$ ifconfig |awk '/eth/{print $1,$5}'
eth0 b8:27:eb:7e:6e:3f

測試複製(cp)速度

#!/bin/bash
begin=`date "+%s%N"`
cp /test/iso /test/qq/iso
end=`date "+%s%N"`
rel=$(($end-$begin))
echo $rel
  • 顯示結果為奈秒。
  • date "+%s%N" 為奈秒

shell script 判斷套件是否已安裝, 如果未安裝自動安裝(自動輸入yes)

if dpkg -s ${package} | grep -q installed; then
    echo "${package} installed"
else
    echo "${package} isn't installed."
    echo "Install ${package}.."
    sudo apt-get --force-yes --yes install ${package}
fi

取得 route IP (Gateway IP)

route -n | grep 'UG[ \t]' | awk '{print $2}'

or :

ip r | awk '/^def/{print $3}'

or :

netstat -rn |awk '{if($1=="0.0.0.0") print $2}'

檢查是否能上網

router_ip=$(netstat -rn |awk '{if($1=="0.0.0.0") print $2}')
ping -c1 ${router_ip} >> /dev/null 2>&1
if [ $? -eq 0 ]; then
    echo OK
else
    echo FAIL
fi

檢查 dns 是否運作正常

# Check DNS
set +e
echo "Check DNS ..."
function check_dns()
{
    is_successful=0
    for ((i=0; i<2; i++)); do
        host www.google.com > /dev/null 2>&1
        if [ $? -eq 0 ]; then
            is_successful=1
            break;
        else
            echo "DNS can't function ..."
            echo "Restart networking and check again ..."
            sudo service networking restart
        fi
    done
    return $is_successful
}
check_dns
if [ $? == 0 ]; then
    echo "DNS can't function ..."
    exit
fi
set -e

選擇 partition (private)

set +e
dev_array=(`echo $(sudo fdisk -l | grep -E '^\/dev\/sda([3-9]|\d{2})' | awk '{print $1}') | cut  -d " " --output-delimiter=" " -f 1-`)
dev_array_count=$((${#dev_array[@]}-1))
until [ "${xfs_choice_confirm}" == "y" ] || [ "${xfs_choice_confirm}" == "Y" ]
do
    df
    sudo fdisk -l
    echo -e "\nDevice list :"
    echo "----------------------"
    for ((i=0; i<=$dev_array_count; i++)); do
        printf " \E[0;33;40m${i}\E[0m : ${dev_array[$i]}\n"
    done
    echo "----------------------"
    printf "Please choose XFS partition by entering a number : "
    read  dev_choice
    echo "${dev_choice}" | grep -o "^[0-9]*$" >> /dev/null 2>&1
    if [ ! $? -eq 0 ] || [ -z ${dev_array[${dev_choice}]} ]; then
        continue
    fi
    printf "You choose \E[0;33;40m${dev_array[${dev_choice}]}\E[0m, are you sure \E[0;31;40m(y/n)\E[0m ? "
    read xfs_choice_confirm
done
xfs_partition=${dev_array[${dev_choice}]}
set -e

顯示資料夾下的檔案及空間

d.sh :

for x in `find /tmp/destination -type f | sort`
do
  sum_y=`sum $x`
  echo "$sum_y $x";
done

顯示結果

$ bash d.sh
34198    19 /tmp/destination/dd.php
63612     3 /tmp/destination/qq/fff/ffddxx.php
53866     2 /tmp/destination/qq/fff/pp.php
07572     1 /tmp/destination/qq/qq.php

第一列數字不清楚 第二列數字(19, 3, 2, 1)是換送成kb的容量

php syntax checking

#!/bin/bash

# For checking php and js syntax

if [ -z $1 ]; then
    echo
    echo "  ***Missing arguments"
    echo
    exit
fi
for PATH in $*
do
    reg=".*(\.php|\.js)"
    if [[ $PATH =~ $reg  ]]; then
        if [ ${BASH_REMATCH[1]} == ".php" ]; then
            printf "\33[0;35;44m$PATH : \33[0m\n"
            /usr/bin/php -l $PATH
            echo "-------------------"
        elif [ ${BASH_REMATCH[1]} == ".js" ]; then
            printf "\33[0;36;44m$PATH : \33[0m\n"
            /usr/bin/sudo -H -u $USER bash -c "$HOME/test_project/bin/jslint -f $PATH"
            echo "-------------------"
        fi
    fi
done

產生亂數字串

This method uses SHA to hash the date, runs through base64, and then outputs the top 32 characters.

jex@jex:~$ date +%s | sha256sum | base64 | head -c 32 ; echo
MmM1MWFjMDVhYzBkMzk3ODRjM2JiZTQ5

This method used the built-in /dev/urandom feature, and filters out only characters that you would normally use in a password. Then it outputs the top 32.

jex@jex:~$ < /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;
udqgTdSRa_zWN1m6E_1iKMzCjvqXUUpD

This one uses openssl’s rand function, which may not be installed on your system. Good thing there’s lots of other examples, right?

jex@jex:~$ openssl rand -base64 32
39iFjCtPB+4zjjsdJJ+FtFjBYPD3GUDRIZtXld5UyRM=

This one works a lot like the other urandom one, but just does the work in reverse. Bash is very powerful!

jex@jex:~$ tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1
Rz27w5YH3FF8D4ughqbgHkHIcgds9T

Here’s an even simpler version of the urandom one.

jex@jex:~$ < /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6
RA0g58

You can even create a random left-hand password, which would let you type your password with one hand.

jex@jex:~$ </dev/urandom tr -dc '12345!@#$%qwertQWERTasdfgASDFGzxcvbZXCVB' | head -c8; echo ""
ZBFC@#AB

And here’s the easiest way to make a password from the command line, which works in Linux, Windows with Cygwin, and probably Mac OS X. I’m sure that some people will complain that it’s not as random as some of the other options, but honestly, it’s random enough if you’re going to be using the whole thing.

jex@jex:~$ date | md5sum
642a1b5de18a71b8585cfe87831d45a5  -

ref: random string

shell scripting and regular expression

echo "$password" | grep -q "[a-z]*[0-9][a-z]*"
if [ $? -eq 0 ] ;then
    echo "match found"
else
    echo "match not found"
fi
  • -q, –quiet, –silent
  • Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if
  • an error was detected. Also see the -s or –no-messages option. (-q is specified by POSIX.)

match $1

reg=".*(\.php)"
if [[ $PATH =~ $reg  ]]; then
    echo ${BASH_REMATCH[1]}
fi

input : t.php

result : .php

多行取代

sed "N;s/xxx/ccc/g" filename 因為加上 N 參數確實可以多行 matching, 但是當你要取代一個範圍(可能預期 match 到 3~6 行做取代) 卻是沒有辦法的..暈

那我將一個檔案讀取出來處理完再存到另一個檔案總沒問題了吧? 原則上是這樣沒錯..但是有一個小問題是行首的空白會不見.. 原來 bash shell 預設斷行是 space, tabs, new line(\n), 不過我們可以控制斷行的字元 - IFS(nternal Field Separator) 這個變數, 當 shell 內部讀取每一行時一遇到 \n 就中斷, 即可解決此問題, 請看以下程式碼 :

# 將檔案儲存到陣列
c=0
OLD_IFS=$IFS
IFS=$'\n'
while read -r line
do
    bashrcArray[$c]=$line
    c=$(expr $c + 1)
done < bashrc

# 這邊不做取代細節, 直接將陣列儲存成檔案
for i in "${bashrcArray[@]}"
do
    echo $i >> bashrcqq
done
IFS=$OLD_IFS
  • 注意 read 要加上 -r 參數, 否則反斜線(\)會被忽略
  • -r : If this option is given, backslash does not act as an escape character. The backslash is considered to be part of the line. In particular, a backslash-newline pair may not be used as a line continuation.

沒想到 shell script 處理多行取代居然會那麼麻煩

Linux Account

顯示 account 權限

show grants for jex@'%';

查詢所屬群組

$ id jex
uid=1000(jex) gid=1000(jex) groups=1000(jex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(sambashare),115(lpadmin)

useradd adduser差別

useradd 只會建立帳號,沒有Home, password, 系統shell

新增 account

sudo adduser newuser
或
RUN useradd --create-home -s /bin/bash jex

將帳號加上 sudo 權限

方法1

加入: 將帳號加入 sudo group

sudo usermod -a -G sudo jex
或
sudo adduser jex sudo
  • -a, –add USER add USER to GROUP
  • -d, –delete USER remove USER from GROUP

移除: 將帳號移除 sudo group

sudo gpasswd -d jex sudo

方法2

加入: 手動新增帳號到 /etc/sudoers :

# User privilege specification
root    ALL=(ALL:ALL) ALL
jex     ALL=(ALL:ALL) ALL

移除: 到 /etc/sudoers 刪除該筆帳號

刪除user

先退出group,再執行

sudo deluser my_account --remove-all-files

or

sudo userdel my_account
sudo rm -rf /home/my_account

List sudo account

執行

getent group sudo | cut -d: -f4

或者去 /etc/group 的 sudo 看

帳號相關資訊

/etc/passwd :

jex:x:1000:1000:jex,,,:/home/jex:/bin/bash
  • 可以改 sh 或 bash
  • 1000 為 uid

切換 www-data 帳號

直接用 su 會跳出錯誤訊息

su www-data
This account is currently not available.

應該用

su -s /bin/bash www-data

無法 ssh www-data 帳號

直接 ssh 是不行的

ssh www-data@localhost -p
www-data@localhost's password:
This account is currently not available.

因為 www-data 是被設定在 /usr/sbin/nologin

Linux 壓縮 (Gzip, Tar)

gzip

Usage: gzip [OPTION]... [FILE]...
Compress or uncompress FILEs (by default, compress FILES in-place).

Mandatory arguments to long options are mandatory for short options too.

  -c, --stdout      write on standard output, keep original files unchanged
  -d, --decompress  decompress
  -f, --force       force overwrite of output file and compress links
  -h, --help        give this help
  -l, --list        list compressed file contents
  -L, --license     display software license
  -n, --no-name     do not save or restore the original name and time stamp
  -N, --name        save or restore the original name and time stamp
  -q, --quiet       suppress all warnings
  -r, --recursive   operate recursively on directories
  -S, --suffix=SUF  use suffix SUF on compressed files
  -t, --test        test compressed file integrity
  -v, --verbose     verbose mode
  -V, --version     display version number
  -1, --fast        compress faster
  -9, --best        compress better
  --rsyncable       Make rsync-friendly archive

With no FILE, or when FILE is -, read standard input.

qq 資料夾下的檔案

$ ls qq
c.txt  d.txt  f.txt  q.txt

壓縮資料夾

$ gzip -r qq
$ ls qq
c.txt.gz  d.txt.gz  f.txt.gz  q.txt.gz

壓縮單檔

$ gzip qq.log
$ ls
$ qq.log.gz

解壓縮

$ gzip -rd qq
$ ls qq
c.txt  d.txt  f.txt  q.txt

gzip僅能壓縮單一檔案

tar

  • -z, –gzip, –gunzip, –ungzip filter the archive through gzip
  • -v, –verbose verbosely list files processed
  • -f, –file=ARCHIVE use archive file or device ARCHIVE

壓縮 qq 資料夾成 qq.tar.gz

tar -zcv -f qq.tar.gz qq

-c, –create create a new archive

查看 qq.tar.gz 裡有哪些檔案

tar -ztv -f qq.tar.gz

-t, –list list the contents of an archive

解壓縮 qq.tar.gz

tar -zxv -f qq.tar.gz

-x, –extract, –get extract files from an archive

解壓縮到 /tmp

tar -zxv -f qq.tar.gz -C /tmp
qq/
qq/d.txt
qq/c.txt
qq/f.txt
qq/q.txt
  • -C, –directory=DIR change to directory DIR
  • 原本的qq目錄也還會在

exclude 忽略檔案或資料夾

tar -zcv -f test.tar.gz test --exclude '.git*' --exclude 'cli.php'

忽略 .git 及 cli.php

gunzip

解壓縮 .gz 的檔案

gunzip access.log-20170725.gz

解壓縮 .zip 無法使用 targzip 來解壓縮

必須使用 zip / unzip 做

安裝 :

sudo apt-get install zip unzip

解壓縮 :

unzip weibo-phpsdk-v2-2013-02-20.zip -d qq

解壓縮並放在 qq 資料夾裡, 如果不加上 -d qq 檔案就會散落在目前資料夾

壓縮 :

zip -r qq.zip qq
  • 將 qq 資料夾壓縮成 qq.zip
  • -r : recurse into directories

PHP 讀寫檔案

php讀寫檔

/home/user_me/test/t.txt :

line 1
line 2
line 3
line 4
line 5

讀:

$test = fopen('/home/user_me/test/t.txt', 'r');
while ( ! feof($test))
{
    echo fgets($test);
}
fclose($test);

feof — Tests for end-of-file on a file pointer

寫:

[1] 將第一行移除

$path = '/home/user_me/test/t.txt';
$contents = file_get_contents($path);
$rows = explode("\n", $contents);
array_shift($rows);
file_put_contents($path , join("\n", $rows));

join — Alias of implode()

[2] 新增到最後一行

$path = '/home/user_me/test/t.txt';
file_put_contents($path, md5(uniqid(rand(), TRUE)), FILE_APPEND);

[3] 控制整份檔案內容

$file_path = '/home/user_me/test/q.txt';
$fp = fopen($file_path, 'w');
$rows[] = 'line1';
$rows[] = 'line2';
$rows[] = 'line3';
fwrite($fp, join("\n", $rows));
fclose($fp);

fputs — Alias of fwrite()

寫 csv 檔:

$file_path = '/home/user_me/test/q.txt';
$fp = fopen($file_path, 'w');
fputcsv($fp, array('field 1', 'field 2', 'field 3'));
fputcsv($fp, array('1', '2', '3'));
fputcsv($fp, array('4', '5', '6'));
fputcsv($fp, array('7', '8', '9'));
fclose($fp);