🖨️

知らなくてもいい '\r'と'\n'と'\r\n'のこと

2022/08/08に公開
9

こんばんは〜! チェリニャン😽です。

改行について

今日は仕事中に気になったことについて書いてみようと思います。
私は javascript でコードを書きながら split() 関数を使いました。
split()関数の中には色んな文字を書けますが、
今回私は並び替えられている文章を一行ずつ切って配列に入れたかったです。

これを
Hi, my name is chaerrynyan. 
I am an engineer.
I sometimes write on Zenn.

このような配列に!
['Hi, my name is chaerrynyan.','I am an engineer.','I sometimes write on Zenn.']

普通なら split('\n') を使いますが、なぜか split('\r\n') を書いても同じ結果が出たので、
普通、macの基本メモで書いたファイルは \nで改行されてsplit('\r\n')が上手く出来なかったのに、
csvファイルをアップロードしたら \r\nで改行されてsplit('\n')もsplit('\r\n')も出来たので、
その理由とどっちがもっと正確な表記なのか知りたかったです。

結論から言うと'\n'を使えばいいです!

(本文を作成する前... 私はmacOSmontereyを使っています。)

split('\r')'\r' は Carriage Return、意味は「行の一番前に移動する」です。
split('\n')'\n' は Line Feed、意味は「次の行に移動する」です。

この言葉は昔のタイプライターから来た言葉です。
typewriter
昔のタイプライターは行を変えるために手動で紙を動かさなければなりませんでした。
Carriage Returnは紙の左の部分タイプライターの左の部分と合わせること、Line Feedは紙を一つの行だけ上に動くことでした。
それで、昔のタイプライターはCarriage Return('\r')とLine Feed('\n')を両方('\r\n')やってこそ、改行処理が可能でした。

パソコンの画面(display)を使ってからはもうCarriage Return('\r')は必要なくなって、
Line Feed('\n')だけでも行を変えることができるようになりました。

でも面白いのはOSによってこの改行の方法が少し違います。

windows : CRLF('\r\n')
unix : LF('\n')
macOS : LF('\n') (バージョン9までは CR, 後からは LF)

改行は今回みたいにsplit()関数を使う時以外にも、ファイルの最後の行に改行を入れる時に重要になります。
この場合は、協業による開発の際、もし開発者同士のOSが異なると問題が発生する可能性があります。
Gitにはこれを解決する方法があります!
Gitによる行終端の扱い方を変更
このような設定をしたら協業する場合も上手く改行処理ができます。
(下記はGitの公式ドキュメントです。)
https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings

今日の記事は以上になります!有難うございました ☺️ ☕️

Discussion

cocoalixcocoalix

こんにちは、とても面白い記事でした
キーボードの配列がタイプライターから来ているのは知っていましたが、
もともとCRとLFが別々のキーに割り当てられていた背景については知りませんでした

ところで、

普通なら split('\n') を使いますが、なぜか split('\r\n') を書いても同じ結果が出たので、
その理由とどっちがもっと正確な表記なのか知りたかったです。

こちらについては再現性がなかったのですが、実行なされた環境をお伺いしてもよろしいでしょうか?
WindowsのChromium系ブラウザ、macOSのSafariなどで試しましたが、いずれもご記載いただいているような形に String.split() が実行されませんでした

実行結果
https://ideone.com/aOOAWP

実行したコード

const x = 'aaaa¥rbbbb¥ncccc¥r¥ndddd';
console.log(x.split('¥r¥n'));
console.log(x.split('¥n'));
console.log(x.split(/[¥r¥n]{1,2}/g));

※追記: 一応MDNのjavascriptの String.split() のドキュメントも貼っておきますね
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split

今年から社会人で、しかも技術更新の激しいWebプログラムの職に就かれたとのことで、
これからも非常に大変だとは思いますが、引き続き頑張ってくださいね
(上から目線のようでしたらごめんなさい、私も頑張りますね!)

chaerrynyanchaerrynyan

こんにちは、コメントしてくださってありがとうございます!😺
コメントを読んで、私が誤解の素地があるように文を作成したと気づきました。

まず、伝えたかった要点は上記に書いたように「\r はそのままが持ってる意味はもうなくなったので、\nも\r\nも同じ役割をしてます。」と「その歴史(?)の説明」でした。

普通、macの基本メモで書いたファイルは\nで改行されてsplit('\r\n')が上手く出来なかったのに、
csvファイルをアップロードしたら\r\nで改行されてsplit('\n')もsplit('\r\n')も出来たのが、
この記事を書いたきっかけでした。

//今回私が使ったファイルの改行
const str1 = 'The quick \r\n brown fox \r\n jumps over \r\n the lazy dog.';
console.log(str1)
/**
* "The quick \r\n brown fox \r\n jumps over \r\n the lazy dog."
*/

//'\n'と'\r\n'の結果が同じ
const n = str1.split('\n');
console.log(n);
/**
* Array ["The quick ", " brown fox ", " jumps over ", " the lazy dog."]
*/

const rn = str1.split('\r\n');
console.log(rn);
/**
* Array ["The quick ", " brown fox ", " jumps over ", " the lazy dog."]
*/

//mac基本メモファイルの改行
const str2 = 'The quick \n brown fox \n jumps over \n the lazy dog.';
console.log(str2)
/**
* "The quick \n brown fox \n jumps over \n the lazy dog."
*/

//'\r\n'ではsplitできない
const n2 = str2.split('\n');
console.log(n2);
/**
* Array ["The quick ", " brown fox ", " jumps over ", " the lazy dog."]
*/

const rn2 = str2.split('\r\n');
console.log(rn2);
/**
* Array ["The quick \n brown fox \n jumps over \n the lazy dog."]
*/

確かにコメントくださった文章を見ると、付加説明が必要だと思いましたので説明をもっと詳しく書きました。
おかげで記事がもっと綺麗になりました 😊 
ありがとうございます。

chaerrynyan 🍒😽

dj oyudj oyu

'\n'と'\r\n'でsplitした結果は違うようですよ。
console.log(rn[0] === n[0]) で確認しました。

chaerrynyanchaerrynyan

n[0]は後ろに \r が付いているので結果が違っだと思います。
\r は気にしなくていいと思いましたが、もしダメな理由があればぜひ教えてください!

dj oyudj oyu

ダメ(もしくはダメじゃない)な理由は運用によるんじゃないでしょうか?
ともかく結果が同じという主張は間違いだとおもいました。

cocoalixcocoalix

'\n'と'\r\n'でsplitした結果は違うようですよ。
console.log(rn[0] === n[0]) で確認しました。

これは正しいと思います、私も検証済みですし、チェリニャンさんのコメントを読んで「ちょっと外れているなぁ」と思ったのも事実です
しかし、チェリニャンさんのバックボーンがなんであれ、社会人経験がまだあまりない人物に対してその指摘は、(私もやってる自覚がありますのであまり強くは言えないのですが、)重箱の隅をつつきすぎていると考え、ここまでにとどめておりました

また、一方で

ダメ(もしくはダメじゃない)な理由は運用によるんじゃないでしょうか?

こちらは非常に厳格な指摘の割に具体例を提示できておらず、チェリニャンさんに対して非常に不親切であると感じました
正直なんでもいいんですけど、「人間は知っていること以外は何も知らない」し、「知っている情報からしか思考を巡らせることはできず、それを逸脱した思考は『空想』や『妄想』と呼ばれる」のですから、「ダメな理由は運用による」という指摘は、現場経験が浅い者に対してあまりにも抽象的すぎて理解に辿り着くことができない回答のように思います

ここまで少し批判をするようなことを言ってしまい、申し訳ございません
ただ、 厳格な指摘をしてはいけないという意図でお話しているのではなく、ご記載いただいているご指摘だと、遠くからただ単にヤジを投げているようにしかみえなかったのでご注意ください ませ、というものです

※追記:

ともかく結果が同じという主張は間違いだとおもいました。

このような「技術情報共有」を行う媒体で、誤った情報を記載するのは良くないよね、という点のご指摘については確かに同意できますし、「結果が同じという主張は間違い」という点についても同意致します

…ので、下記にその抽象を具象に落としてみますね

想定しているシステムの前提として

  • ECサイトのお客様からの受注と、それに対する商品の発送のシステム
  • 月に1度、ECサイトを運営している会社は、運送会社の利用履歴を運用会社からCSVで受領し、ECサイト上の発送履歴との突合(データの一致チェック)を実施する

という感じにします

運送会社の利用履歴としては例えば次のようなCSVデータを考えます

receive_date,shipped_date,pref_code,shipping_code
2022-08-01 11:00:00,2022-08-02 16:35:00,1,e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
2022-08-03 15:00:00,2022-08-04 18:29:00,2,60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752

CSVの各カラムについては

  • receive_date は運送会社さんが荷物を受領した日時
  • shipped_date は運送会社さんが荷物をお届け完了した日時
  • pref_code は配達先の都道府県
  • shipping_code は荷物の個別の識別コード

と思ってください

さて、このCSVを…まぁあんまりないかもしれませんが、Node.jsで動いているExpress環境によるサーバサイド環境もありますので、javascript続投で行きますね

コード長すぎてこれコメントじゃなくねってなったのでコードはgistに移動しました
https://gist.github.com/cocoalix/23281d5f2037bb99394b3c51053a6e24

あと中身ちゃんとoutputしてくれないけどideoneでも検証済み
https://ideone.com/s3eCeJ

で、以下実行結果を抜粋します

cocoalix@Aki:~/winhome/Documents$ node csvToJson.js
[
  {
    receive_date: '2022-08-01 11:00:00',
    shipped_date: '2022-08-02 16:35:00',
    pref_code: '1',
    'shipping_code\r': 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\r'
  },
  {
    receive_date: '2022-08-03 15:00:00',
    shipped_date: '2022-08-04 18:29:00',
    pref_code: '2',
    'shipping_code\r': '60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752\r'
  }
]

この状態って、例えばこの結果を x に入れたとして x[0]['shipping_code'] ってできますでしょうか…?
結果はもうgistにも記載しておりますが、 undefined となってしまい、値が取れなかったりします
他にも、例えば x[0]['shipping_code'] で仮に適切に荷物の識別コードが取得できたとしても、突合時に \r が消えておりませんので、 if (x === y) をしたときに true にならず、不具合となります
このような動作の不具合を招くことがありますので dj oyu さんはご指摘してくださったのだと思います

長文失礼いたしました
せっかくの社会人生活初めて?の夏休みですので、ゆっくり羽根を広げてくださいませ

cocoalixcocoalix

あともうひとつ追記 (本記事を記述する上でもうご理解されているとは思います)
チェリニャンさんは

普通、macの基本メモで書いたファイルは\nで改行されてsplit('\r\n')が上手く出来なかったのに、
csvファイルをアップロードしたら\r\nで改行されてsplit('\n')もsplit('\r\n')も出来たのが、
この記事を書いたきっかけでした

と記載いただいておりますが、記事内で言及されております通り、WindowsとほかのOSでは、採用している改行コードが異なります
そのため、もし、異なる改行コードに対応をするのであれば、 mattn さん(非常に大先輩の方ですね!!ちょっとびっくりしました!) が仰る通り

str1.split(/\r?\n/)

を採用するのが適切かもしれません
あるいは、私もチラ見せしておりましたが

x.split(/[¥r¥n]{1,2}/g)

という方法もあります
どちらも共通して、 正規表現 を用いて String.split() を実行しています
よろしければ、正規表現はバリデーションチェックなどでも避けては通れない道となっておりますので、お調べしてみてはいかがでしょうか?

引き続き失礼いたしました

mattnmattn

Windows でも Mac でも Linux でも改行コードを取り除いて分割したいという事であれば

str1.split(/\r?\n/)
[ 'The quick ', ' brown fox ', ' jumps over ', ' the lazy dog.' ]

とするのが良いかと思います。