read – シェルスクリプトで入力を受け取るコマンド

コマンドリファレンス

read コマンドは、標準入力から 1行の入力を読み取り、変数に格納 するためのシェル組み込みコマンドです。
対話的にユーザー入力を受け付けたり、スクリプト内で入力値を処理する際に利用されます。

構文(Syntax)

read [オプション] [変数...]

主なオプション一覧

オプション説明使用例
(なし)入力を1行読み取り、指定した変数に格納read name
-p "PROMPT"入力前にメッセージを表示read -p "Enter your name: " name
-s入力を非表示(パスワード入力向け)read -s password
-t N入力を待つ秒数を指定(タイムアウト)read -t 5 name
-n NN文字だけ読み取るread -n 1 key
-rバックスラッシュをエスケープ文字として扱わないread -r line
-a ARRAY入力を配列に格納read -a items
-d DELIM区切り文字を指定(デフォルトは改行)read -d : input

実行例

基本的な入力の読み取り

read name
echo "Hello, $name"

実行例:

Alice   ← ユーザー入力
Hello, Alice

プロンプト付きで入力

read -p "Enter your name: " name
echo "Welcome, $name"

パスワードを非表示で入力

read -s -p "Password: " password
echo
echo "Password entered (hidden)"

5秒以内に入力がなければタイムアウト

read -t 5 -p "Enter your name: " name

1文字だけ入力を取得

read -n 1 -p "Press any key: " key
echo "You pressed '$key'"

改行やエスケープをそのまま読み取る

read -r line

配列として入力を格納

read -a items
echo "First item: ${items[0]}"

入力例:

apple banana cherry

出力例:

First item: apple

区切り文字(:)までを入力として取得

echo "abc:def:ghi" | read -d : var
echo "$var"

出力例:

abc

エラー例(タイムアウト)

read -t 2 name

出力例:

(2秒経過後に終了、変数は空)

csvファイルの取り扱い

read コマンドを使うことでcsvファイルに記載された情報による処理の繰り返し実行に対応できます。

1) 基本:カンマ区切り(引用・カンマ埋め込みなし想定)

#!/usr/bin/env bash
# basic_csv_read.sh
# 例: ./basic_csv_read.sh sample.csv

set -o errexit -o nounset -o pipefail

csv_file="${1:-sample.csv}"

# ヘッダーを読み飛ばしたいときは tail -n +2 を使う
tail -n +2 "$csv_file" | while IFS=, read -r id name age; do
  # 空行スキップ
  [[ -z "${id}${name}${age}" ]] && continue
  printf 'id=%s, name=%s, age=%s\n' "$id" "$name" "$age"
done
  • 前提:"Alice, Bob" のようなフィールド内カンマ二重引用符が無いCSV向け。

2) 可変列数を配列で受ける(列数がバラつくCSV向け)

#!/usr/bin/env bash
# csv_to_array.sh
# 例: ./csv_to_array.sh sample.csv

set -o errexit -o nounset -o pipefail
csv_file="${1:-sample.csv}"

# ヘッダー飛ばし
{ read -r _header; cat; } < "$csv_file" | \
while IFS=, read -r -a cols; do
  [[ ${#cols[@]} -eq 0 ]] && continue
  echo "列数=${#cols[@]} / 先頭=${cols[0]}"
  # 例: 3列目を安全に参照
  if (( ${#cols[@]} >= 3 )); then
    echo "3列目=${cols[2]}"
  fi
done
  • read -a で配列に展開できます(ただし引用・埋め込みカンマは未対応)。

3) RFC4180 風:ダブルクォート対応(埋め込みカンマ・二重の二重引用符 “” を扱う)

Bash だけで素朴にパースする小さな関数。改行を含むフィールドは未対応ですが、一般的な "a, b","x""y" を処理できます。

#!/usr/bin/env bash
# csv_read_rfcish.sh
# 例: ./csv_read_rfcish.sh sample_quoted.csv

set -o errexit -o nounset -o pipefail

csv_read_line() { # usage: csv_read_line array_name ; reads 1 logical line from stdin
  local __arr_name=$1
  local line
  IFS= read -r line || return 1

  local -a fields=()
  local field="" c next in_quotes=0
  local i=0 len=${#line}

  while (( i < len )); do
    c=${line:i:1}
    if (( in_quotes )); then
      if [[ $c == '"' ]]; then
        next=${line:i+1:1}
        if [[ $next == '"' ]]; then
          field+='"'         # 連続する "" はエスケープされた "
          ((i++))
        else
          in_quotes=0        # クォート終了
        fi
      else
        field+="$c"
      fi
    else
      case "$c" in
        '"') in_quotes=1 ;;  # クォート開始
        ',') fields+=("$field"); field="" ;;
        $'\r') ;;            # CR 無視(Windows 改行対策)
        *) field+="$c" ;;
      esac
    fi
    ((i++))
  done
  fields+=("$field")

  # nameref 経由で呼び出し側の配列に代入(bash 4.3+)
  local -n __ref=$__arr_name
  __ref=("${fields[@]}")
  return 0
}

csv_file="${1:-sample_quoted.csv}"

# ヘッダーを先に1行読む(必要なければ削除)
{
  IFS= read -r header || true
  echo "HEADER: $header"
  while csv_read_line cols; do
    # 空行スキップ
    [[ ${#cols[@]} -eq 1 && -z ${cols[0]} ]] && continue
    printf 'Row:'
    for ((i=0; i<${#cols[@]}; i++)); do
      printf ' [%d]=<%s>' "$i" "${cols[$i]}"
    done
    printf '\n'
  done
} < "$csv_file"
テスト用 sample_quoted.csv
id,name,comment
1,"Alice","Hello, world"
2,"Bob","He said ""Good luck!"" yesterday"
3,"Carol","no quotes"

4) CSVを書き出す(値を安全にエスケープして出力)

#!/usr/bin/env bash
# csv_write.sh
# 例: ./csv_write.sh > out.csv

set -o errexit -o nounset -o pipefail

csv_escape() {
  local s=$1
  s=${s//\"/\"\"}  # " を "" に
  if [[ "$s" == *[,\"$'\n'$'\r']* ]]; then
    printf '"%s"' "$s"
  else
    printf '%s' "$s"
  fi
}

# 見出し
printf '%s,%s,%s\n' "id" "name" "note"

# データ例
id=1;   name='Alice'; note='Hello, world'
printf '%s,%s,%s\n' \
  "$(csv_escape "$id")" \
  "$(csv_escape "$name")" \
  "$(csv_escape "$note")"

id=2;   name='Bob';   note='He said "Good luck!"'
printf '%s,%s,%s\n' \
  "$(csv_escape "$id")" \
  "$(csv_escape "$name")" \
  "$(csv_escape "$note")"

補足(実務Tips)

  • 行末 \r が混ざるCSV(Windows由来)は、読み取り時に read -r しつつ上の実装のように \r を無視するか、事前に dos2unix 等で正規化すると安定します。
  • 完全なCSV互換(改行入りフィールドなど)を厳密にやるなら、mlr, awk(FPAT), python -c 'import csv' などの専用パーサを併用するのが安全です。

IFSの役割と読み込み時の注意点

read は入力を IFS(Internal Field Separator) で区切って変数に格納します。デフォルトのIFSは半角スペース・タブ・改行です。複数の変数を指定した場合、余った分はすべて最後の変数にまとめて入ります。

echo "a b c d" | { read w1 w2 rest; echo "w1=$w1 / w2=$w2 / rest=$rest"; }
# → w1=a / w2=b / rest=c d

CSVのようにカンマ区切りで読みたい場合は、IFS=, のように一時的にIFSを変更します。

IFS=, read -r col1 col2 col3 <<< "a,b,c"
echo "$col1 / $col2 / $col3"
# → a / b / c

while readループでハマりやすい罠

command | while read line; do ... done のようにパイプで while read に渡すと、ループ内がサブシェルで実行されます。そのため、ループ内で変更した変数はループの外から参照できません。

# NG例:countがループ外で0のまま
count=0
seq 1 3 | while read -r n; do
  count=$((count + 1))
done
echo "count=$count"
# → count=0(サブシェル内でしか更新されていない)
# OK例:プロセス置換でサブシェル化を避ける
count=0
while read -r n; do
  count=$((count + 1))
done < <(seq 1 3)
echo "count=$count"
# → count=3

詳しくは サブシェルの落とし穴|変数消失の防止 も参照してください。

シェル(bash/sh/zsh/ksh)による read の違い

read は各シェルの組み込みコマンドですが、オプションの対応状況が異なります。sh(dash)向けにスクリプトを書く場合は特に注意してください。

オプションbashdash(sh)zshksh
-p(プロンプト)×
-s(非表示)×
-t(タイムアウト)×
-a(配列)×△(-A
-n(文字数指定)×

#!/bin/sh で始まるスクリプトが dash で実行される環境(Debian/Ubuntu系)では、-p-a などのbash拡張オプションはエラーになります。移植性を重視するなら read var の基本形とリダイレクト・パイプの組み合わせに留めるのが安全です。

readarray / mapfile との違い(配列読み込み)

read -a は1行分の入力を空白区切りで配列に格納しますが、複数行をまとめて配列化したい場合は mapfilereadarray)を使います。

mapfile -t lines < sample.txt
echo "行数: ${#lines[@]}"
echo "1行目: ${lines[0]}"

1行ずつ処理しつつ変数に安全に格納したいなら while read、ファイル全体を配列として一括保持したいなら mapfile が適しています。詳しくは mapfile – 標準入力を配列に読み込む を参照してください。

よくある質問(FAQ)

Q. read -r を付けないとどうなる?

A. -r を付けないと、入力中のバックスラッシュ(\)がエスケープ文字として解釈され、改行の継続やバックスラッシュの消失など意図しない挙動になることがあります。ファイルパスなど \ を含む可能性のある入力を扱う場合は、常に read -r を使うのが安全です。

Q. read が入力を待たずにすぐ終了してしまう

A. 標準入力がすでにパイプやリダイレクトで別のデータに置き換わっている、もしくは -t でタイムアウトが短く設定されている可能性があります。対話的に入力させたい場合は、標準入力が端末(/dev/tty)に接続されているか確認してください。

Q. shスクリプトでread -pやread -sが使えない

A. -p/-s/-t/-a などはbash拡張のオプションで、POSIX準拠の sh(dash)では未対応です。#!/bin/sh のスクリプトでは printf でプロンプトを表示してから read var を呼ぶ形に書き換えてください。

関連コマンド

  • echo : 変数やメッセージの出力
  • printf : 書式付きの出力
  • select : シェル組み込み、メニュー形式で選択を受け付け
  • IFS と read -r の安全読込 : 空白・改行耐性のある読み込み方
  • mapfile : 標準入力を配列に読み込む(複数行の一括取得)
  • while / until / for : while readでの実務活用

備考

  • read は多くのシェル(bash, zsh, ksh, dash)において シェル組み込み コマンドです。
  • read で指定した変数は、そのシェルの環境でのみ有効。外部コマンドではありません。
  • パスワード入力のように非表示にする場合は -s を利用しますが、シェルの種類によって挙動が異なる場合があります。

参考

使い方のテクニック紹介

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント