エックスサーバーでWP-CLIを使う方法|PHPのバージョンエラー対策と投稿73件の一括更新手順

公開日
エックスサーバーでWP-CLIを使う方法|PHPのバージョンエラー対策と投稿73件の一括更新手順

エックスサーバーではSSHでWP-CLIを使えます。ところが、そのまま wp コマンドを打つと古いPHPで動いてしまい、エラーで止まることがあります。

私も、BudsBlue.netのブログ記事をまとめて更新しようとして、最初にこのエラーでつまずきました。この記事では、エックスサーバーのSSH接続からWP-CLIのPHPバージョン指定、実際に投稿73件の本文を一括更新したときの手順とスクリプトまでをまとめます。

目次

結論:PHPのバージョンを指定して実行し、関数にまとめておく

  • エックスサーバーのSSHは、サーバーパネルでONにし、公開鍵を登録すれば使えます(ポートは10022)
  • 筆者のサーバーでは、wp を実行するとPHP 5.4.16で動いていました。/usr/bin/php8.3 /usr/bin/wp のようにPHPのバージョンを指定して実行します
  • 毎回長いコマンドを打たずに済むよう、~/.bashrc に関数として登録しておくと楽になります
  • 投稿の一括更新は「バックアップ → 確認のみの実行 → 少数だけ実行 → 全件」の順で進めると安全です

この記事の環境

項目内容
サーバーエックスサーバー(レンタルサーバー)
手元のPCMac(ターミナル)
サーバー上のWP-CLI2.4.0(/usr/bin/wp)
実行に使ったPHP8.3(/usr/bin/php8.3)
確認日2026年10月21日

WP-CLIのバージョンや、コマンドラインで使われるPHPのバージョンは、サーバーによって異なる可能性があります。自分の環境は、後述の wp --info で確認してください。

エックスサーバーでSSHを使う準備

手順の詳細は、エックスサーバー公式のSSH設定マニュアルにもまとまっています。

サーバーパネルでSSHをONにする

サーバーパネルの「SSH設定」を開き、状態を「ON」に変更します。

公開鍵を登録する

同じ「SSH設定」画面で「公開鍵を登録」をクリックします。登録方法は2つあります。

  • 自動生成:ラベルを入力して登録すると、秘密鍵(サーバーID.key)がダウンロードされます。パスフレーズも設定できます
  • 手動登録:手元で作った公開鍵(OpenSSH形式)を貼り付けて登録します

エックスサーバーのSSHは公開鍵認証のみで、パスワードでは接続できません。

MacからSSHで接続する

ダウンロードした秘密鍵を ~/.ssh/ に移動し、自分だけが読めるように権限を変更します。

chmod 600 ~/.ssh/xxxx.key

次のコマンドで接続します。xxxx の部分は自分のサーバーIDに置き換えてください。ホスト名は サーバーID.xsrv.jp のほか、サーバー番号(sv***.xserver.jp)でも接続できます。

ssh -i ~/.ssh/xxxx.key -p 10022 xxxx@xxxx.xsrv.jp

初めて接続するときは「Are you sure you want to continue connecting」と確認されるので、yes と入力します。鍵にパスフレーズを設定した場合は、続けて入力を求められます。

~/.ssh/config に登録しておくと楽

毎回長いコマンドを打つのが面倒なら、Mac側の ~/.ssh/config に接続先を登録しておきます。

Host xserver
  HostName xxxx.xsrv.jp
  Port 10022
  User xxxx
  IdentityFile ~/.ssh/xxxx.key

登録すれば、次のコマンドだけで接続できます。

ssh xserver

WP-CLIが古いPHPで動いてしまう問題とPHPバージョンの指定

症状:構文エラーで止まる

私が最初に公開投稿を一括で下書きに戻そうとしたとき、wp コマンドがPHPの構文エラーで止まってしまいました。

原因を確認する

原因は、wp を動かしているPHPのバージョンでした。wp --info で確認できます。

wp --info

筆者のサーバーでは、次のように表示されました(一部抜粋)。

Shell: /bin/bash
PHP binary: /usr/bin/php
PHP version: 5.4.16
WP-CLI version: 2.4.0

SSHで php を実行したときの既定のバージョンが、PHP 5.4.16だったのです。WordPressのサイト自体はサーバーパネルで設定した新しいPHPで動いていても、コマンドラインでは別のバージョンが使われることがあります。

PHPのバージョンを指定して実行する

エックスサーバーには、バージョンごとのPHPが用意されています。たとえばPHP 8.3なら /usr/bin/php8.3 です。これを使って、WP-CLIの本体(/usr/bin/wp)を実行します。

/usr/bin/php8.3 /usr/bin/wp --info

指定するバージョンは、サーバーパネルでそのサイトに設定しているPHPバージョンに合わせておくと安心です。

Deprecatedの警告を抑える

PHP 8.3で実行すると、今度は「Deprecated(非推奨)」の警告が表示されました。Deprecatedは「この書き方は将来のPHPで使えなくなる予定」という予告です。現時点では処理は続きますが、出力が警告で埋まって、肝心の結果やエラーが見づらくなります。

そこで -d オプションで、Deprecatedの警告だけを表示しないようにします。

/usr/bin/php8.3 -d "error_reporting=E_ALL & ~E_DEPRECATED" /usr/bin/wp --info

この指定で非表示になるのは、PHP本体が出す E_DEPRECATED だけです。エラー(Error)や注意(Warning)、WordPressやプラグインが出す E_USER_DEPRECATED は対象外です。また、WordPressは読み込みの途中で独自にエラー表示の設定をするため、警告の出方はWP-CLIやWordPressのバージョン、WP_DEBUG の設定によって変わります。

警告を隠すだけなので、問題そのものが解決するわけではありません。表示が見づらいときに使う方法と考えてください。

毎回打たずに済むよう関数にまとめる

このコマンドを毎回打つのは大変なので、wpcli という関数にまとめます。サーバーの ~/.bashrc の末尾に、次の3行を追記してください。

wpcli() {
  /usr/bin/php8.3 -d "error_reporting=E_ALL & ~E_DEPRECATED" /usr/bin/wp "$@"
}

保存したら、設定を読み込み直して動作を確認します。

source ~/.bashrc
wpcli --info

「PHP version: 8.3.x」と表示されれば成功です。

SSHでログインしたときは、通常 ~/.bash_profile が読み込まれます。筆者のサーバーでは、~/.bash_profile の中で ~/.bashrc を読み込む設定になっていたため、.bashrc に書いた関数がログイン時に使えるようになりました。念のため、自分の環境でも確認しておきましょう。

cat ~/.bash_profile

if [ -f ~/.bashrc ]; then . ~/.bashrc; fi のような記述があれば大丈夫です。

私は最初、この関数を .bashrc に書かず、ログインするたびにコマンドラインで定義し直していました。SSHを切断すると関数は消えてしまうので、はじめから .bashrc に書いておくことをおすすめします。

なお、この関数を使えるのは、ログインして手で打つコマンドだけです。シェルスクリプトや xargs の中からは呼び出せません(後述します)。

実務でよく使うWP-CLIコマンド

ここからは、前述の wpcli 関数を登録した前提で書きます。各コマンドの詳しい使い方は、WP-CLI公式のコマンド一覧で確認できます。サーバーに入っているWP-CLIが古い場合、公式ドキュメントに載っているオプションが使えないこともあります。

WordPressの場所を指定する

WP-CLIは、操作するWordPressの場所を --path で指定できます。対象のサイトを取り違えないよう、この記事では毎回 --path を付けます。

# WordPressの場所を変数に入れておく(自分の環境に合わせて変更)
WP_PATH=/home/xxxx/example.com/public_html

wpcli --path="$WP_PATH" core version

エックスサーバーでは、ドメインごとの公開ディレクトリが ~/ドメイン名/public_html です。WordPressをサブディレクトリにインストールしている場合は、wp-config.php があるディレクトリまで指定します。

投稿やカテゴリーの情報を確認する

まずは何も変更しない、確認用のコマンドから試すのがおすすめです。

# WordPressのバージョンを確認
wpcli --path="$WP_PATH" core version

# 公開中の投稿数を数える
wpcli --path="$WP_PATH" post list --post_type=post --post_status=publish --format=count

# 投稿のID・状態・スラッグを一覧で確認(先頭20件)
wpcli --path="$WP_PATH" post list --post_type=post --post_status=any --format=table --fields=ID,post_status,post_name | head -20

# 特定の投稿の情報を確認
wpcli --path="$WP_PATH" post get 123 --fields=ID,post_type,post_status,post_name,post_title

# カテゴリーの一覧を確認
wpcli --path="$WP_PATH" term list category --format=table --fields=term_id,name,slug,count

データベースをバックアップする

投稿をまとめて変更する前には、必ずデータベースを書き出しておきます。

保存先を指定しないと、コマンドを実行したディレクトリにファイルが作られます。public_html の中で実行すると、データベースの中身がWebから見える場所に置かれてしまうおそれがあります。保存先は、公開ディレクトリの外にフルパスで指定してください。

# 公開ディレクトリの外に、自分だけが読める保存用フォルダを作る
mkdir -p ~/wp-backups
chmod 700 ~/wp-backups

# 保存先をフルパスで指定して書き出す
wpcli --path="$WP_PATH" db export ~/wp-backups/backup-$(date +%Y%m%d-%H%M).sql
chmod 600 ~/wp-backups/*.sql
ls -l ~/wp-backups

エックスサーバーでは、ホームディレクトリ(~/)の直下は公開ディレクトリの外です。

万一のときは、db import でバックアップから戻せます。

# 復元の前に、今の状態もバックアップしておく
wpcli --path="$WP_PATH" db export ~/wp-backups/before-restore-$(date +%Y%m%d-%H%M).sql

# バックアップから復元する(ファイル名は自分のものに置き換える)
wpcli --path="$WP_PATH" db import ~/wp-backups/backup-20260717-1716.sql

db import を実行すると、データベースはバックアップを取った時点の内容で上書きされます。バックアップ後に増えたコメントや問い合わせ、ほかの投稿の変更もすべて元に戻ってしまうので、復元の前に今の状態もバックアップしておきましょう。

作業が終わったバックアップは、手元のPCにダウンロードしてサーバーから削除するなど、置きっぱなしにしないようにします。

投稿をまとめて下書きに戻す

公開中の投稿をまとめて下書きに戻すコマンドは、実行するとサイトから記事がすべて消えます。最初にバックアップを取り、対象を確認しながら進めてください。

# 1. データベースをバックアップする
wpcli --path="$WP_PATH" db export ~/wp-backups/before-draft-$(date +%Y%m%d-%H%M).sql
chmod 600 ~/wp-backups/*.sql

# 2. 対象の件数を確認する
wpcli --path="$WP_PATH" post list --post_type=post --post_status=publish --format=count

# 3. 対象の投稿を一覧で確認する
wpcli --path="$WP_PATH" post list --post_type=post --post_status=publish --fields=ID,post_title,post_name

# 4. 1件だけ下書きにして、サイトで確認する(123は一覧で確認したIDに置き換える)
wpcli --path="$WP_PATH" post update 123 --post_status=draft

1件で問題がなければ、残りをまとめて実行します。

# 5. 残りをまとめて下書きにする
wpcli --path="$WP_PATH" post update $(wpcli --path="$WP_PATH" post list --post_type=post --post_status=publish --format=ids) --post_status=draft

# 6. 公開中の投稿が0件になったか確認する
wpcli --path="$WP_PATH" post list --post_type=post --post_status=publish --format=count

post update には、IDを複数並べて渡せます。$( ) の中で公開中の投稿のIDを取り出し、まとめて下書きにしています。実行すると1件ごとに「Success: Updated post …」と表示されるので、「Error」が出ていないか確認してください。最後の件数確認で「0」と表示されれば完了です。

xargsを使う場合の注意

IDの一覧を xargs で渡す書き方もよく見かけます。ただし、前述の wpcli はシェルの関数なので、xargs からは呼び出せません。xargs は外部のコマンドしか実行できないためです。xargs を使う場合は、後ろにPHPとWP-CLIの本体を直接書きます。

# NG:wpcli は関数なので、xargs からは呼び出せない(実行しないでください)
# wpcli --path="$WP_PATH" post list --post_type=post --post_status=publish --format=ids | xargs -r wpcli post update --post_status=draft

# OK:xargs の後ろは、PHPとWP-CLIの本体を直接書く
wpcli --path="$WP_PATH" post list --post_type=post --post_status=publish --format=ids \
  | xargs -r /usr/bin/php8.3 -d "error_reporting=E_ALL & ~E_DEPRECATED" /usr/bin/wp --path="$WP_PATH" post update --post_status=draft

私が最初に下書きへ戻したときは、この xargs の書き方で実行しました。

URLを一括で置換する

サイトの移転やURLの変更では、search-replace でデータベース内のURLをまとめて置き換えられます。データベースを広く書き換える操作なので、バックアップを取ったうえで、まず --dry-run で件数を確認してください。

# まずは --dry-run で、どのテーブルで何件置換されるかだけ確認する
wpcli --path="$WP_PATH" search-replace 'https://example.com/old/' 'https://example.com/new/' --precise --dry-run

# 問題なければ --dry-run を外して実行し、キャッシュを削除する
wpcli --path="$WP_PATH" search-replace 'https://example.com/old/' 'https://example.com/new/' --precise
wpcli --path="$WP_PATH" cache flush

テーブルの指定を省略すると、WordPressが管理しているテーブルだけが置換の対象になります。--all-tables を付けると、同じデータベースにあるWordPress以外のテーブルまで書き換わるため、通常は付けない方が安全です。プラグインが独自に作ったテーブルも置換したいときは、テーブル名の先頭(接頭辞)が同じテーブルだけを対象にする --all-tables-with-prefix を使います。

実例:WP-CLIで投稿73件の本文を一括更新した手順

BudsBlue.netでは、リライトしたブログ記事73件の本文とタイトルを、WP-CLIでまとめて反映しました。管理画面で1件ずつ貼り替えると、貼り間違いや更新漏れが起きやすい作業です。

このとき重視したのは、次の3点です。

  • 投稿IDで対象を指定し、スラッグ(URL)は変更しない
  • いきなり全件を更新せず、確認のみの実行を挟む
  • 1件ごとの結果をログに残す

準備したもの

サーバー上に作業用のフォルダを作り、次のファイルを置きました。

  • html/:記事ごとの本文HTMLファイル
  • posts.tsv:投稿ID・スラッグ・HTMLファイル名・新しいタイトルの対応表
  • update_posts_safe.sh:一括更新のスクリプト(後述)

posts.tsv はタブ区切りで、1行目は見出しです。

id	slug	file	title
123	sample-post	sample-post.html	新しい記事タイトル
124	another-post	another-post.html	もう一つの記事タイトル

作業用のフォルダは、公開ディレクトリ(public_html)の外に作ります。

手順1:データベースをバックアップする

前述の db export で、更新前の状態を公開ディレクトリの外に書き出しておきます。

手順2:確認のみで実行する

スクリプトは、初期状態では何も更新しない「確認のみ」で動くようにしてあります。投稿が存在するか、投稿タイプとスラッグが一致するか、HTMLファイルがあるかを1件ずつ確認し、結果をログに出します。

ここで確認できるのは「更新の対象と材料が揃っているか」までです。確認のみの実行では、最後に「確認OK=件数」と表示されますが、これは更新に成功した件数ではありません。実際の更新が成功するかどうかは、更新を実行するまで分かりません。

本番環境での動作確認結果

2026年10月21日、エックスサーバーの本番環境で、記事掲載用に安全対策を追加した最新版スクリプトのDRY-RUNを実施しました。

対象となる73記事すべてについて、投稿ID・投稿タイプ・スラッグ・HTMLファイルの確認が正常に完了しました。

完了(確認のみ・更新していません): 確認OK=73 失敗=0 スキップ=0 処理=73

終了コードは 0 でした。

なお、これは更新前の確認処理が正常に動作した結果です。最新版スクリプトによる本番の書き込み処理まで検証したものではありません。

“`

手順3:少数だけ更新して表示を確かめる

LIMIT=3 を付けると、TSVの先頭3件だけを本番更新します。更新後にブラウザで記事を開き、本文やタイトルが正しく反映されているかを確認します。

手順4:全件を更新する

問題がなければ、LIMIT=0 で全件を更新します。このとき、手順3で更新した先頭の3件も、もう一度同じ内容で更新されます。同じHTMLで上書きされるだけなので、内容は変わりません。

# 1. 確認のみ(何も更新しない)
DRY_RUN=1 bash update_posts_safe.sh

# 2. 先頭3件だけ本番更新
DRY_RUN=0 LIMIT=3 bash update_posts_safe.sh

# 3. 全件を本番更新
DRY_RUN=0 LIMIT=0 bash update_posts_safe.sh

# 終了コードを確認する(0=すべて成功 / 1=失敗あり / 2=スキップあり)
echo $?

実行が終わると、最後に件数のまとめが表示されます。73件の更新は、すべて成功しました。

[1/73] OK: ID=123 slug=sample-post
[2/73] OK: ID=124 slug=another-post
...
----------------------------------------
完了: 成功=73 失敗=0 スキップ=0 処理=73 DRY_RUN=0

「失敗」や「スキップ」が1件でもあれば、ログファイルで理由を確認してください。スクリプトの終了コードは、すべて成功なら0、失敗があれば1、失敗はないがスキップがあれば2になります。

スクリプト全文

実際に使ったスクリプトをもとに、記事への掲載にあたって次の安全策を追加した版です。追加した安全対策は、ダミーのWP-CLIによるテストに加え、実際のエックスサーバー環境でもDRY-RUNによる73件の事前確認を実施しています。ただし、最新版スクリプトによる本番更新は行っていません。

  • スラッグが一致しない投稿は、更新せずにスキップする(元のスクリプトは警告を出して更新を続けていました)
  • 使うPHPとWP-CLI本体の場所を設定欄で指定し、存在を確認する
  • LIMIT に数字以外が指定されたら止める
  • TSVの最終行に改行がなくても読み込む
  • TSVに空欄があっても列がずれないように読み込み、スラッグやファイル名が空の行はスキップする
  • 投稿情報(投稿タイプ・ステータス・スラッグ・タイトル)を1回の wp post get で取得する(元のスクリプトは1件につき5回実行していました)
  • 確認のみの実行では「成功」ではなく「確認OK」と表示し、更新の成功と区別する
  • スキップがあったときも、終了コードで分かるようにする

冒頭の「設定」部分を、自分の環境に合わせて変更してください。wp post update では、本文のファイルをIDの後ろに指定すると、本文がそのファイルから読み込まれます(公式ドキュメント)。本文をコマンドの引数に展開しないので、長いHTMLや改行・記号を含む本文でも、そのまま渡せます。

#!/usr/bin/env bash
# 既存投稿の本文・タイトルを投稿ID指定で安全に一括更新する
# 終了コード: 0=すべて成功 / 1=失敗あり / 2=失敗はないがスキップあり

set -uo pipefail

# ===== 設定 =====
WP_PATH="/home/xxxx/example.com/public_html"      # wp-load.php がある場所
HTML_DIR="./html"                                 # HTMLファイルを置くフォルダ
TSV="./posts.tsv"                                 # id / slug / file / title
DRY_RUN="${DRY_RUN:-1}"                           # 1=確認のみ(更新しない) / 0=更新実行
UPDATE_TITLE="${UPDATE_TITLE:-1}"                 # 1=タイトルも更新 / 0=本文のみ
LIMIT="${LIMIT:-0}"                               # 0=全件 / 3=先頭3件だけ
EXPECTED_POST_TYPE="post"                         # 通常投稿だけを対象にする
PHP_BIN="/usr/bin/php8.3"                         # WP-CLIを動かすPHP
WP_BIN="/usr/bin/wp"                              # WP-CLI本体
LOG_FILE="./update_posts_$(date +%Y%m%d_%H%M%S).log"
# ================

WP=("$PHP_BIN" -d "error_reporting=E_ALL & ~E_DEPRECATED" "$WP_BIN" "--path=${WP_PATH}")

log() {
  printf '%s\n' "$*" | tee -a "$LOG_FILE"
}

# 事前チェック
if [ ! -x "$PHP_BIN" ]; then
  echo "ERROR: ${PHP_BIN} が見つかりません。PHP_BINを確認してください"
  exit 1
fi

if [ ! -f "$WP_BIN" ]; then
  echo "ERROR: ${WP_BIN} が見つかりません。WP_BINを確認してください"
  exit 1
fi

if [ ! -f "${WP_PATH}/wp-load.php" ]; then
  echo "ERROR: ${WP_PATH}/wp-load.php が見つかりません。WP_PATHを確認してください"
  exit 1
fi

if [ ! -f "$TSV" ]; then
  echo "ERROR: $TSV が見つかりません"
  exit 1
fi

if [ ! -d "$HTML_DIR" ]; then
  echo "ERROR: $HTML_DIR が見つかりません"
  exit 1
fi

if [ "$DRY_RUN" != "0" ] && [ "$DRY_RUN" != "1" ]; then
  echo "ERROR: DRY_RUN は 0 または 1 を指定してください"
  exit 1
fi

if [ "$UPDATE_TITLE" != "0" ] && [ "$UPDATE_TITLE" != "1" ]; then
  echo "ERROR: UPDATE_TITLE は 0 または 1 を指定してください"
  exit 1
fi

if ! [[ "$LIMIT" =~ ^[0-9]+$ ]]; then
  echo "ERROR: LIMIT は 0 以上の整数を指定してください"
  exit 1
fi

total=$(awk -F '\t' 'NR > 1 && $1 != "" && $1 != "\r" { count++ } END { print count+0 }' "$TSV")
ok=0
checked=0
ng=0
skip=0
n=0

log "開始: DRY_RUN=${DRY_RUN} UPDATE_TITLE=${UPDATE_TITLE} LIMIT=${LIMIT} 対象候補=${total}"
log "ログ: ${LOG_FILE}"

# ヘッダーを除いて1行ずつ処理。パイプにしないためカウントが保持される
# 「|| [ -n "$line" ]」は、最終行に改行がない場合も読み込むため
while IFS= read -r line || [ -n "$line" ]; do
  # Windows改行対策
  line=${line%$'\r'}

  # タブはBashでは空白扱いになり、空欄があると列が詰まるため、
  # 空白扱いされない区切り文字(\x1f)に置き換えてから分割する
  IFS=$'\x1f' read -r id slug file title <<< "${line//$'\t'/$'\x1f'}"

  [ -z "$id" ] && continue

  n=$((n + 1))
  if [ "$LIMIT" -gt 0 ] && [ "$n" -gt "$LIMIT" ]; then
    break
  fi

  path="${HTML_DIR}/${file}"

  if [ -z "$slug" ]; then
    log "[${n}/${total}] SKIP: TSVのスラッグが空です ID=${id}"
    skip=$((skip + 1))
    continue
  fi

  if [ -z "$file" ]; then
    log "[${n}/${total}] SKIP: TSVのファイル名が空です ID=${id}"
    skip=$((skip + 1))
    continue
  fi

  if ! [[ "$id" =~ ^[0-9]+$ ]]; then
    log "[${n}/${total}] SKIP: 投稿IDが数値ではありません ID=${id}"
    skip=$((skip + 1))
    continue
  fi

  if [ ! -f "$path" ]; then
    log "[${n}/${total}] SKIP: HTMLファイルなし ID=${id} file=${file}"
    skip=$((skip + 1))
    continue
  fi

  if [ ! -s "$path" ]; then
    log "[${n}/${total}] SKIP: HTMLファイルが空です ID=${id} file=${file}"
    skip=$((skip + 1))
    continue
  fi

  # 投稿の存在・投稿タイプ・ステータス・スラッグ・タイトルを1回で取得する
  post_info=$("${WP[@]}" post get "$id" --fields=post_type,post_status,post_name,post_title --format=json 2>>"$LOG_FILE")
  if [ $? -ne 0 ] || [ -z "$post_info" ]; then
    log "[${n}/${total}] FAIL: 投稿が見つからないか取得できません ID=${id}"
    ng=$((ng + 1))
    continue
  fi

  # JSONをPHPで読み、\x1f区切りの1行にする(タイトル内の改行やタブは空白に置き換える)
  fields=$(printf '%s' "$post_info" | "$PHP_BIN" -r '
    $p = json_decode(stream_get_contents(STDIN), true);
    if (!is_array($p)) { exit(1); }
    $out = [];
    foreach (["post_type", "post_status", "post_name", "post_title"] as $k) {
      $out[] = str_replace(["\x1f", "\t", "\r", "\n"], " ", (string) ($p[$k] ?? ""));
    }
    echo implode("\x1f", $out);
  ' 2>>"$LOG_FILE")
  if [ $? -ne 0 ]; then
    log "[${n}/${total}] FAIL: 投稿情報を読み取れません ID=${id}"
    ng=$((ng + 1))
    continue
  fi
  IFS=$'\x1f' read -r current_type current_status current_slug current_title <<< "$fields"

  if [ "$current_type" != "$EXPECTED_POST_TYPE" ]; then
    log "[${n}/${total}] SKIP: 投稿タイプ不一致 ID=${id} type=${current_type}"
    skip=$((skip + 1))
    continue
  fi

  # 投稿IDの取り違えを防ぐため、スラッグが一致しない投稿は更新しない
  if [ "$current_slug" != "$slug" ]; then
    log "[${n}/${total}] SKIP: スラッグ不一致 ID=${id} TSV=${slug} 現在=${current_slug}"
    skip=$((skip + 1))
    continue
  fi

  # <file> を指定すると、本文はファイルから読み込まれる(--post_content は不要)
  args=(post update "$id" "$path")
  if [ "$UPDATE_TITLE" -eq 1 ] && [ -n "$title" ]; then
    args+=("--post_title=${title}")
  fi

  if [ "$DRY_RUN" -eq 1 ]; then
    log "[${n}/${total}] DRY-RUN: ID=${id} status=${current_status} slug=${current_slug}"
    log "  現在タイトル: ${current_title}"
    if [ "$UPDATE_TITLE" -eq 1 ] && [ -n "$title" ]; then
      log "  更新タイトル: ${title}"
    else
      log "  タイトル: 変更なし"
    fi
    log "  本文ファイル: ${path} ($(wc -c < "$path" | tr -d ' ') bytes)"
    checked=$((checked + 1))
    continue
  fi

  error_file=$(mktemp)
  if "${WP[@]}" "${args[@]}" > /dev/null 2>"$error_file"; then
    log "[${n}/${total}] OK: ID=${id} slug=${current_slug}"
    ok=$((ok + 1))
  else
    error_message=$(tail -n 3 "$error_file" | tr '\n' ' ')
    log "[${n}/${total}] FAIL: ID=${id} slug=${current_slug} ${error_message}"
    ng=$((ng + 1))
  fi
  rm -f "$error_file"

done < <(tail -n +2 "$TSV")

log "----------------------------------------"
if [ "$DRY_RUN" -eq 1 ]; then
  log "完了(確認のみ・更新していません): 確認OK=${checked} 失敗=${ng} スキップ=${skip} 処理=${n}"
else
  log "完了: 成功=${ok} 失敗=${ng} スキップ=${skip} 処理=${n} DRY_RUN=${DRY_RUN}"
fi

if [ "$ng" -gt 0 ]; then
  exit 1
fi

if [ "$skip" -gt 0 ]; then
  exit 2
fi

exit 0

主な安全策は次のとおりです。

  • DRY_RUN の初期値を「1(確認のみ)」にして、うっかり本番更新しないようにしている
  • 投稿タイプが post 以外ならスキップし、固定ページなどを誤って書き換えない
  • TSVのスラッグと現在のスラッグが違うとき、またはTSVのスラッグが空のときはスキップし、投稿IDの取り違えによる誤更新を防ぐ
  • HTMLファイルが存在しない、または空のときはスキップする
  • 1件ごとの結果と、失敗時のエラー内容をログファイルに残す

スクリプトの中では、前述の wpcli 関数ではなく、PHPとWP-CLI本体のパスを直接指定しています。bash で実行するスクリプトからは、.bashrc の関数は使えないためです。

wp post get の結果はJSONで受け取り、設定欄で指定したPHPで読み取っています。jq などの追加のツールは必要ありません。

同じ考え方で、SEOプラグイン(AIOSEO)のメタディスクリプションをまとめて更新したときも、確認のみ → 3件 → 全件の順で進めました。

注意点

本番サイトで実行する前にバックアップを取る

WP-CLIは管理画面と違い、確認の画面を挟まずに即座にデータを書き換えます。更新や削除のコマンドを実行する前には、必ず db export でバックアップを取ってください。

バックアップファイルを公開ディレクトリに置かない

バックアップには、データベースの中身がすべて含まれます。保存先は公開ディレクトリの外にし、作業が終わったら手元に保存してサーバーからは削除しましょう。

サーバー上のWP-CLIのバージョンを確認しておく

筆者のサーバーに入っていたWP-CLIは2.4.0でした。WP-CLIの公式ドキュメントに載っているオプションの中には、古いバージョンでは使えないものもあります。うまく動かないときは、wp --info でバージョンを確認してください。

まとめ:エックスサーバーのWP-CLIは、PHPの指定と安全な手順がポイント

  • エックスサーバーのSSHは、サーバーパネルでONにして公開鍵を登録すれば使える(ポート10022)
  • WP-CLIが古いPHPで動いてエラーになるときは、/usr/bin/php8.3 /usr/bin/wp のようにPHPのバージョンを指定する
  • 長いコマンドは ~/.bashrc に関数として登録しておく
  • 投稿の一括更新は、バックアップ → 確認のみ → 少数テスト → 全件の順で進める

管理画面で何十件も手作業していた更新が、WP-CLIなら安全に、まとめて終わります。複数のWordPressサイトを管理している方は、ぜひ試してみてください。

これからエックスサーバーを契約する方へ

私がエックスサーバーを使い続けている理由や、法人向けのXServerビジネスとの違いは、エックスサーバーのメリット・デメリット|制作歴15年の本音と選び方にまとめています。

IPPEI FUKUTA(株式会社バッズブルー 代表)

IPPEI FUKUTA(株式会社バッズブルー 代表)

WordPress専門のWeb制作者として15年・150サイト以上を担当。現場でつまずいた問題と解決策を、同じ制作者・フリーランスの方に向けて発信しています。無料のWeb便利ツール集「バドツール」を開発・運営中。