カテゴリー
プログラム 社内SE

sshのconfigのhostをリスト表示し、そのままssh

sshコマンドを打つときに.configに設定したホスト名を忘れて躓く方のための補助用のスクリプトです。
Windows Powershell版Linux bash版それぞれあります。個人用に作成しましたので細かいエラーはご容赦のほど。

使い方

コマンド:sshfc [ホスト名の一部]

sshfcとだけ打つとconfigファイルに設定した全ホスト名が表示されます。
sshfc sysのように第一引数をつけて打つとsysを含むホスト名が表示されます。続けて番号選択でそのままSSH接続です。
コマンド名は”ssh from config” の頭をとってsshfcとしています。以下使用例になります。

C:\> sshfc sys
1) system
2) test_system
3) dev_system
SSHするホストの番号を入力してください。[c]を押すとキャンセルされます。: 3
ssh dev_system
hogehoge@192.168.1.100's password:
....

Windows Powershell版

スクリプト

sshfc.ps1として以下のファイルを作成し、SJISかBOM付UTF-8で保存します。(普通のUTF-8はNGです。実行時に文字化けします。メモ帳の[名前を付けて保存]で UTF-8 BOM付きを選択)

using namespace Microsoft.VisualBasic

# paramater
Param($targetHost)
if(-not ([string]::IsNullOrEmpty($targetHost))){
	$targetHost = $targetHost.trim().ToLower()
}else{
	$targetHost = ""
}

# local
$jap = $FALSE
$local = Get-WinSystemLocale
if ($local.Name.Contains('ja')){
	$jap = $TRUE
}

# config file path
$configFile = $HOME + "\.ssh\config"
if( $(test-path $configFile) -ne $True ){
	if ($jap){
		echo "設定ファイル(.ssh\config)がありません"
	}else{
		echo "the .ssh\config file doesn't exist..."
	}
	exit 1
}

# read the config file
$hostNames = @()
$lines = get-content $configFile
foreach($line in $lines){
	$command, $hostname = $line.trim() -split '\s+|\t+'
	# if ($command -eq "host" -Or $command -eq "hostname"){
	if ($command -eq "host"){
		if ($targetHost -eq ""){
			$hostNames += $hostname
		}else{
			if ($hostname.ToLower().Contains($targetHost)){
				$hostNames += $hostname
			}
		}
	}
}

$lenHostNames = $hostNames.Count
if ($lenHostNames -eq 0){
	if ($targetHost -eq ""){
		if ($jap){
			write-host("設定ファイルにホストが見つかりません。")
		}else{
			write-host("didn't find any hosts in the config file.")
		}
	}else{
		if ($jap){
			write-host("[" + $targetHost + "]を持つホストが見つかりません。")
		}else{
			write-host("didn't find any hosts including [" + $targetHost + "].")
		}
	}
	exit 1
}

for ( $index = 0; $index -lt $lenHostNames; $index++){
	write-host ( ([string]($index+1)) + ") " + $hostNames[$index] )
}

$strInput = ""
if ($jap){
	$strInput = "SSHするホストの番号を入力してください。[c]を押すとキャンセルされます。"
}else{
	$strInput = 'Press the number of the host to ssh. Press [c] if you want to cancel.'
}

Add-Type -AssemblyName "Microsoft.VisualBasic"

# loop until valid data comming or 10 times mistake
$bOKInput = $FALSE;
$input = ""
for ($index = 0; $index -lt 10; $index++){
	$input = Read-Host($strInput);
	$input = [Strings]::StrConv($input, [VbStrConv]::Narrow)
	if ($input.ToLower() -eq "c"){
		exit 1
	}
	if ($input -match "[0-9]+"){
		$input = [int]$input
		if ($input -le 0 -Or $input -gt $lenHostNames){
			if ($jap){
				$strInput = "認識できません。番号を再入力してください。"
			}else{
				$strInput = 'Failed to get the number. Press the number again. '
			}
			continue
		}else{
			$bOKInput = $TRUE
			break;
		}
	}
}
if ($bOKInput){
	write-host("ssh " + $hostNames[$input-1])
	ssh $hostNames[$input-1]
}

exit 0

スクリプトの設置

以下をPowershellで実行し、ユーザディレクトリにbinフォルダを作成します。

New-Item ($HOME + "\bin") -ItemType Directory

続いて以下のコマンドでメモ帳を起動

notepad $profile

メモ帳が起動し、Windows terminalの設定ファイルが開かれるのでメモ帳に以下を記載します。(初めて設定する場合はブランクのファイルが開きますので以下を記載。)

$env:path += ";" + $HOME + "\bin"
$OutputEncoding = [System.Text.Encoding]::GetEncoding('utf-8')

VSCODEなど他のソフトでも使う場合は、以下のコマンドでPATHを設定します。(こちらを使用する場合は上記のWindows terminalの設定は不要です。)

[System.Environment]::SetEnvironmentVariable("Path", $env:Path + ";%USERPROFILE%\bin", "User")

スクリプトファイル(sshfc.ps1)をユーザディレクトのbinフォルダにコピーします。フォルダの場所がわからない場合は、以下のコマンドで表示される場所がコピーする場所になります。

$HOME + "bin"

これでPowershellからsshfcコマンドが使えるようになるはずです。

注意事項

スクリプト自体が実行できない場合は以下コマンドを実行してみてください。スクリプトの実行制限を緩めます。

Set-ExecutionPolicy Unrestricted -scope CurrentUser

Linux Bash版

スクリプト

以下のファイルをsshfcとして作成します。Ubuntuで使っているのでShebangは#!/bin/bashにしています。動かない場合はそれぞれの環境のbashのパスにしてください。

#!/bin/bash

#paramater
targetName=""
if [ $# -gt 0 ]; then
    targetName=$1
fi

SSH_CONFIG=$HOME/.ssh/config
if [ ! -e $SSH_CONFIG ]; then
    echo "couldn't find the ssh config file. [$(SSH_CONFIG)]"
    exit 0
fi

aryHosts=()
while read line
do
    # trim
    echo $line > line
    # replace tabs to spaces
    echo ${line//\t/" "} > line
    # sumup spaces to a space
    echo "$line" | sed -e 's/  */ /g' > line
    # is this target line?
    if [ -n "$line" ] && [ "`echo $line | grep -i '^host '`" ]; then
        hostname="${line:5}"
        if [ -n "$targetName" ]; then
            if [ "`echo $hostname | grep -i $targetName`" ]; then
                aryHosts+=($hostname)
            fi
        else
            aryHosts+=($hostname)
        fi
    fi

done < $SSH_CONFIG

i=1
for hostname in "${aryHosts[@]}"
do
    echo "$i) $hostname"
    ((i++))
done

# 20 is a safty limit to avoid an infinity loop
for ((i=0; i < 20; i++)); do
    read -p 'Enter the number to ssh. C will be canceled. : ' selectedNum
    if [ $selectedNum = "C" ] || [ $selectedNum = "c" ]; then
        exit 0
    fi

    # number entered?
    if expr "$selectedNum" : "[0-9]*$" >&/dev/null;then
        if [ $selectedNum -gt 0 ] && [ $selectedNum -le ${#aryHosts[@]} ]; then
            #echo ${aryHosts[$(($selectedNum-1))]}
            echo "ssh ${aryHosts[$(($selectedNum-1))]}"
            ssh ${aryHosts[$(($selectedNum-1))]}
            break
        fi
    fi
done

exit 0

配置方法

rootで~/sshfcというファイルで作ったとして以下のコマンドで実行権限付与し、PATHの下へファイル移動します。

chmod 777 ~/sshfc
mv ~/sshfc /usr/local/bin

雑記

最近、WindowsのsshクライアントをMicrosoft謹製の「Windows terminal」に変更しました。 「Windows terminal」 自体は秀逸なのですが、以前使用していたsshクライアントと比べ若干使いずらいのが、SSHの接続先の選択方法。なのでツールを作ってカバーした感じです。

作ったツールが思いのほか便利だったので、bash版も作成しサーバに入った後のSSHも同様にできるようにしました。「.configに設定したホスト名なんだっけ?」と考えることが無くなりました。

Powershell のスクリプトを書くのは初めて。こういった作業効率化のちょっとしたコードを書く分には構文に慣れている分、過去のvbscriptで良いかなあという印象です。bashのスクリプトも簡単な数行ものを除けば、めったに書かないのでいつも構文忘れます。

Powershell版は日英つけていますがps1ファイルは配布が難しいので意味ないです。Bash版は面倒なので英語だけです。

カテゴリー
プログラム 社内SE

PHP ヤマト配送状況取得 2021年サイト変更対応

2021年9月 クロネコヤマトの荷物問い合わせシステムのページレイアウトが変更になって、送り状番号から荷物の配送状況を取得していたバッチがエラーとなった。
APIを使用すればよいという話だが、ヤマトビジネスメンバーズは他部署で契約&契約した人は退職で詳細不明になっている。まあ、面倒なので、PHPで問い合わせページから荷物状況を取得するプログラムを書いた。

実際はベタ書きしているが、かっこ悪いので、公開に当たりクラス化。1回テスト確認はしたが、使っていないので、エラーがああるかも。PHP5.4だが、7でも8でも動くと思う。取得した状況には「▶」が付いているので不要であれば消して使ってください。
内部でサイト解析のためDOMDocument使っているので使える環境のこと。以下、使い方とクラス。

使い方

$truckNumbers = array(
'444486855064',
'4444-9099-9090',
'444490999101',
); // いくつでも可

$objYamato = new clsGetYamatoDeliverStatus();
$aryDelivStatus = $objYamato->getYamatoDeliverStatus($truckNumbers);
if ($aryDelivStatus === false){ /* エラー ヤマト追跡番号 状況取得失敗 */ }

var_dump($aryDelivStatus);
/*
結果は[
(‐無しの追跡番号),
(‐有りの追跡番号),
状況日,
配達状況,
問い合わせ時間]の配列
array(3) {
  [0] =>
  array(5) {
    [0] =>
    string(12) "444486855064"
    [1] =>
    string(14) "4444-8685-5064"
    [2] =>
    string(5) "10/20"
    [3] =>
    string(16) "▶ 配達完了"
    [4] =>
    string(19) "2021/10/12 09:59:10"
  }
  [1] =>
  array(5) {
    [0] =>
    string(12) "444490999090"
    [1] =>
    string(14) "4444-9099-9090"
    [2] =>
    string(5) "10/20"
    [3] =>
    string(16) "▶ 配達完了"
    [4] =>
    string(19) "2021/10/12 09:59:10"
  }
  [2] =>
  array(5) {
    [0] =>
    string(12) "444490999101"
    [1] =>
    string(14) "4444-9099-9101"
    [2] =>
    string(5) "10/20"
    [3] =>
    string(16) "▶ 配達完了"
    [4] =>
    string(19) "2021/10/12 09:59:10"
  }
}
*/

荷物状況取得 クラス

<?php

class clsGetYamatoDeliverStatus{

	// 配列$truckNumbersに指定された追跡番号の状況を全て取得する。$truckNumbersは‐有でも無しでも可。
	// 戻り値は次の配列の配列 ["123456781234"(‐無しの追跡番号),"1234-5678-1234"(‐有りの追跡番号),mm/dd 状況日,配達状況,Y/m/d H:i:s 問い合わせ時間]
	// 戻り値がfalseの場合、エラー(ヤマト追跡サイトにアクセス出来ない)
	public function getYamatoDeliverStatus($truckNumbers){
		// 10件ずつヤマトのサイトからトラッキング情報を取得する。
		$numnerCnt = count($truckNumbers);
		$getTruckNumbers = array();
		$aryAllStatusInfo = array();
		for ($i=0;$i < $numnerCnt;$i++){
			$getTruckNumbers[] = $truckNumbers[$i];
			if (count($getTruckNumbers) == 10){
				$aryRetStatus = $this->getYamatoDeliverStatusCore($getTruckNumbers);
				$aryTemp = array_merge($aryAllStatusInfo, $aryRetStatus);
				if ($aryTemp === false){ return false; }
				$aryAllStatusInfo = $aryTemp;
				$getTruckNumbers = array();
			}
		}

		// 残っている場合
		if (count($getTruckNumbers) > 0){
			$aryRetStatus = $this->getYamatoDeliverStatusCore($getTruckNumbers);
			$aryTemp = array_merge($aryAllStatusInfo, $aryRetStatus);
			if ($aryTemp === false){ return false; }
			$aryAllStatusInfo = $aryTemp;
		}

		return $aryAllStatusInfo;
	}

	// ヤマトの追跡サイトにアクセスし、10件づつ結果を取得する。
	// 引数の$truckNumbersは配列で10件まで。
	// 戻り値は次の配列の配列 ["123456781234"(‐無しの追跡番号),"1234-5678-1234"(‐有りの追跡番号),mm/dd 状況日,配達状況,Y/m/d H:i:s 問い合わせ時間]
	// 戻り値がfalseの場合エラー(サイトにアクセス出来ない場合のみ)
	private function getYamatoDeliverStatusCore($truckNumbers){
		$aryRet = array();

		$url = 'https://toi.kuronekoyamato.co.jp/cgi-bin/tneko';
		$tnumCnt = count($truckNumbers);

		$aryPOST = array(
			'mypagesession' => '',
			'backaddress' => '',
			'backrequest' => '',
			'number00' => 2
		);
		$j=0;
		for ($i=1;$i<=10;$i++){
			$postName = 'number';
			if ($i < 10){
				$postName .= '0' . $i;
			}else{
				$postName .= $i;
			}
			if ($j < $tnumCnt){
				$aryPOST[$postName] = $truckNumbers[$j];
			}else{
				$aryPOST[$postName] = '';
			}
			$j++;
		}

		$http_build = http_build_query($aryPOST,'','&');
		$header = array(
			"Content-Type: application/x-www-form-urlencoded",
			"Content-Length: ".strlen($http_build)
		);
		// オプション
		$opts = array("http" => array(
			"method" => "POST",
			"header"  => implode("\r\n", $header),
			"content" => $http_build)
		);
		$scc = stream_context_create($opts);

		// 読み取り
		$get_html = file_get_contents($url, false, $scc);
		if ($get_html === false){ return false; }

		// staus取得時間
		$getTime = Date('Y/m/d H:i:s');

		$dom = new DOMDocument();
		$ret = $dom->loadHTML($get_html);

		$divResult = $this->getElementsByClassName($dom, 'tracking-box-area', 'div');
		$divLen = count($divResult);
		for($i=0;$i < $divLen;$i++){
			// このdivの下のinputのvalueを取得
			$domInputs = $this->getDomNodeFromTagName($divResult[$i], 'input');
			if (count($domInputs) == 0){ continue; }
			$trNum = $domInputs[0]->attributes->getNamedItem('value')->nodeValue;
			if ($trNum == ''){ continue; }

			// このdiv以下の日付を取得
			$date = '';
			$dateNodes = $this->getDomNodeFromClassName($divResult[$i], 'data date');
			if (count($dateNodes) == 0){ continue; }
			$date = $dateNodes[0]->nodeValue;

			// このdiv以下の状況を取得
			$state = '';
			$dateStates = $this->getDomNodeFromClassName($divResult[$i], 'data state');
			if (count($dateStates) == 0){ continue; }
			$state = $dateStates[0]->nodeValue;

			$aryRet[] = array(
				str_replace('-','',$trNum), // ‐なし
				$trNum, // ‐あり
				$date, // 状況になった日付
				$state, // 状況
				$getTime // 取得時刻
			);

		}

		return $aryRet;
	}

	private function getElementsByClassName($dom, $ClassName, $tagName=null) {
		if($tagName){
			$Elements = $dom->getElementsByTagName($tagName);
		}else {
			$Elements = $dom->getElementsByTagName("*");
		}
		$Matched = array();
		for($i=0;$i<$Elements->length;$i++) {
			if($Elements->item($i)->attributes->getNamedItem('class')){
				if(strpos($Elements->item($i)->attributes->getNamedItem('class')->nodeValue, $ClassName) !== false) {
					$Matched[]=$Elements->item($i);
				}
			}
		}
		return $Matched;
	}

	// 子ノード以下に指定のタグ名を持つdomNodeの一覧を返す。
	private function getDomNodeFromTagName($domNode, $tagName){
		$aryRet = array();
		foreach ( $domNode->childNodes as $chiledNode ){
			if ($chiledNode->nodeName == $tagName){
				$aryRet[] = $chiledNode;
			}
			if ($chiledNode->hasChildNodes()){
				$aryTemp = $this->getDomNodeFromTagName($chiledNode, $tagName);
				foreach($aryTemp as $temp){
					$aryRet[] = $temp;
				}
			}
		}
		return $aryRet;
	}

	// 子ノード以下に指定のクラス名を持つdomNodeの一覧を返す。
	private function getDomNodeFromClassName($domNode, $className){
		$aryRet = array();
		foreach ( $domNode->childNodes as $chiledNode ){
			if ($chiledNode->hasAttributes()){
				$nodAttr = $chiledNode->attributes->getNamedItem('class');
				if ($nodAttr != null){
					$nodeClasses = $nodAttr->nodeValue;
					if (strpos($nodeClasses, $className) !== false){
						$aryRet[] = $chiledNode;
					}
				}
			}
			if ($chiledNode->hasChildNodes()){
				$aryTemp = $this->getDomNodeFromClassName($chiledNode, $className);
				foreach($aryTemp as $temp){
					$aryRet[] = $temp;
				}
			}
		}
		return $aryRet;
	}
}
カテゴリー
DIY 家族

コーナー用テレビ台のベビーガード

うちのテレビは部屋の角に置いているが、9か月の子供がテレビをバンバンたたくようになって、一部液晶が白っぽくなってしまった。

これ以上たたかれると視聴が困難になるので、ベビーガードを作ることにした。

作ったベビーガード。ただこれでは嫁の許可が下りず最終系は一番下に。
途中経過は省くが、こういうのを作って、合わせて完成
1x4材を4本使っている。この時点での費用は1400円ぐらい。
横は90cm、高さは48cm
わかりにくいがL字に合わせたところ。裏から見た図。
大量のネジとともにL字の金具なども使って丈夫にしている。

これで完成と思いきや、設置したら息子の力でも簡単に滑ってしまう。
100均に行ってマットの滑り止め4枚入りを2個買ってくる。

ペタペタと6枚貼ってこんな感じ。
不安だったが、意外に効果があり、息子の力では滑らなくなった。

嫁さんが角が危ないというので、上はタオルをかけて、足の部分は西松屋で買って忘れてしまいこんでたガードをつけて完成。

最終系

まあタオルは再利用するとして、だいたい2000円ちょっとぐらいの費用と思われる。木材の調達から作成まで3,4時間ぐらいかかった。塗装はなし。

タオルで隠れているが、真ん中が開いていて、そのうち子供が足かけて登りそう。次に作ることがあったら、壁はすのこ状にして足かけたりできないようにするかな。。

カテゴリー
DIY 家族

隙間ガード

8か月の息子がコンセントに指を突っ込まないか嫁さんが心配しているので、100均やらアマゾンやらでコンセントガードを買って使ってみたが、小さいのは、つまんで取るし、両面テープで貼るタイプのものは、後ろのカバーごと引きはがしてしまう。

あの細い豚さんの鼻みたいなところに、息子の指が入るとは思えないので、放置していたのだが、テレビなどのコンセントをつなげっぱなしのところは危ないのでDIYで対応。

幸運なことに、うちのコンセントつなげっぱなしで危なそうなところは1か所のみ、以下のような隙間である。

ここをガードしてしまえば良いので1x4材で枠を2つ作ってつなげてはめてガードにした。

こんな枠を2つ
つなげてこんな感じ。
奥のほうはコンセントがぶつかるのでコの字型の枠。
よよよと挿入
だいたいぴったし。

ガード入れる前は隙間に手を突っ込んだり、コード引っ張ったりで大変だったが、ガード入れてからは興味を失ったぽいので、とりあえず成功かな・・。

費用は1x4材2枚で600円ぐらい。

カテゴリー
プログラム 社内SE

Pear Mailで送信したファイルを 送信済みフォルダに設置する。

IMAPで使用しているメールアドレスを使ってメールを送信し、送信したメールを送信済みフォルダに入れたい事案があった。
解決方法がググっても出てこなかったので、自己解決。

基本的には、PHPのPear Mailで送信し、送信したデータをIMAP関数で送信済みのフォルダに設置する。

以下ソース。送信の細かいところは省略。

include_once("Mail.php");
include_once("Mail/mime.php");

mb_language("uni");
mb_internal_encoding("UTF-8");

// 宛先とヘッダーとボディを作る。省略。以下mimeの例
$mime = new Mail_mime();
$mime->setHTMLBody('something<br />anything');
$body = $mime->get(array(
  'text_encoding' => '7bit',
  'text_charset' => 'UTF-8',
  'html_charset' => 'UTF-8',
  'head_charset' => 'UTF-8'));
$headers = $mime->headers(array(/*省略*/));
$recipients = array(/*省略*/);

// メール送信
$mailObject = Mail::factory("smtp", Array(/*省略*/)); 
$send = $mailObject->send($recipients, $headers, $body);
if (PEAR::isError($send)){ return 'Mail send error! ' . $send->getMessage(); }

////////////////////////////////////////////////////////////////
// IMAPフォルダに設定。

// 送信済みフォルダに設置するデータ作成
$message = '';
foreach($headers as $key => $value){
  $message .= $key . ': ' . $value . "\r\n";
}
$message .= "\r\n" . $body;
// 設置フォルダー
$folderName = '送信済み'; // 自分のIMAPの送信済みフォルダ
$encodeFolder = mb_convert_encoding($folderName, 'UTF7-IMAP', 'UTF-8'); // エンコード
// imap_openの説明を読んで。SSLならポート以降を:993/imap/ssl
$path = "{imap.hogehoge.com:143/imap/notls}INBOX." . $encodeFolder;
$imapStream = imap_open($path,'username@hogehoge.com','password');
if ($imapStream === FALSE){ die(); }
$result = imap_append($imapStream, $path, $message, "\\Seen");
if ($result === FALSE){ die(); }
imap_close($imapStream);

/* end */

以上

カテゴリー
プログラム 社内SE

cordova-plugin-buildinfoのiOSでDebugがfalseになる。

Cordovaのplugin cordova-plugin-buildinfoを利用した際に、iOSでdebugビルドしても”BuildInfo.debug”がfalseになって困っていたところ、以下の方法で解決できたので、備忘録として残しておく。

1)プラグインのバージョンを4.0以上に上げる。
関係ないが、4.0以下だと「このプラグインのせいでiOSのアプリが異常に遅くなることがある」との投稿があったので、とりあえず4.0以上に上げた。

2)XCODEでプロジェクトを開きBuild Settingsから以下2点を変更する。

・Apple LLVM 9.0 – Preprocessing -> Preprocessor Macrosの下にdebugという項目があるので、そこに”DEBUG=1″を追加する。(”Macros”で設定項目を検索するとすぐ見つかる)
・Swift compiler – Custom Flags -> Other Swift Flags
の下にdebugという項目があるので、”-D DEBUG”を追加する。
(”Swift”で設定項目を検索すると見つかる。)

※XCodeプロジェクトの開き方は、Cordova環境直下の./platforms/iosの下に拡張子”.xcworkspace”のファイルがあるのでこれを開く。同じようなファイルで拡張子”.xcodeproj”があるが、こちらではないので注意。

これで、デバッグビルドの場合のみ[BuildInfo.debug]がfalseとなった。

カテゴリー
社内SE 窓口対応

qmail メール送信時エラー( TLS connect failed )までが長い

qmailでメール送信後、数日してから「メールが送信できなかった」とのエラーメールが返ってくるとの相談があった。

エラーメールの内容はだいたい以下のような感じ。

Hi. This is the qmail-send program at xxxxxxxx.
I'm afraid I wasn't able to deliver your message to the following addresses.
This is a permanent error; I've given up. Sorry it didn't work out.

XXXXXXX
TLS connect failed: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1
alert protocol version; connected to xxxxxxxxx
I'm not going to try again; this message has been in the queue too long.

以下のような場合もある。
LS connect failed: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3

どうも相手先のサーバに接続できなかった場合、延々とリトライをしているようだ。
設定を調べてみると、設定ファイル「/var/qmail/control/queuelifetime」に秒数を設定するとその時間以上はリトライをしなくなるらしい。デフォルト値は7日(604800)であった。送れなかったらさっさとエラーを返してほしいが、デフォルト値が長いのにあまり短くするのも怖いので、とりあえず1時間(3600)を設定した。

vi /var/qmail/control/queuelifetime
---
3600
---

ファイルには数字のみ記入する。再起動が必要なので以下をして終了。

qmailctl stop
qmailctl start

qmailctl restartはプロセスが残ってしまうので当方環境ではNGだった。

カテゴリー
社内SE 窓口対応

PDFを印刷した際に一部のページでフォントがおかしくなる

「フォントがおかしい」1年に数回はこの厄介な質問を受けます。今回も、「PDFを印刷したら、XXページ目のフォントだけ違う、何とかしてくれ」という問い合わせが来ました。

該当のPDFを表示した段階では全く違いがわからなかったものの、印刷後の紙で確認すると確かに一部のページで若干文字が濃くなっている感じがします。

PDF自体はシステムから出力していて、特定のページでフォントを変えるような処理はしていませんし、そのシステムを組んでから数年、そんな質問が来たことはありませんでした。

「印刷機の可能性が高く、どうにもなりませんよ。」と回答しかけたところで、思いあたりました。

「PDFを表示するソフトのせいか」

試してみると、Windows 10の旧EdgeでPDFを表示し印刷した時だけ特定のページでフォントがほんの少し濃くなります。Adobe ReaderやChromeで表示、印刷したときは大丈夫なので、旧Edgeが原因なのは間違いなさそうです。

なので「面倒だけど、Edgeで表示したPDFをダウンロードして、それからChromeで表示して印刷してください。」と回答して終わりです。

まあ、面倒だからEdgeで印刷するのでしょうけど。。

カテゴリー
OS 社内SE

FreeBSD : sshやtelnetでログインしたまま、ターミナルを閉じてしまった場合の対処方法

掲題ままですが、残った仮想端末のプロセスを削除する方法を書いておきます。通常、ターミナルを落としたら、プロセスも消えるのでこのような状況にはならないのですが、不安なときの確認にもなるので。。。

  1. ターミナルから切断したユーザでログインし、以下のコマンドを打ち、現在の仮想端末の番号を確認します。
    > who
    以下のような結果が出ます。
    taro  pts/0 6月 16 04:52 (xxxxxx.or.jp)
    taro  pts/1 6月 16 04:56 (xxxxxx.or.jp)
    最も新しくログインしたものが自分なので、ここではpts/1が現在のターミナルです。
     
  2. そのまま以下のコマンドを実行し、仮想端末のプロセスを探します。
    > ps -aux | grep `whoami`
  3. 以下のような結果が表示されます。
    root 27048 0.0 1.4 20428 7976 - Is 04:52 0:00.02 sshd: taro [priv] (sshd) 
    taro 27050 0.0 1.4 20432 8008 - S 04:52 0:00.02 sshd: taro@pts/0 (sshd) 
    root 27124 0.0 1.4 20428 8012 - Is 04:56 0:00.02 sshd: taro [priv] (sshd) 
    taro 27126 0.0 1.4 20432 8036 - I 04:56 0:00.00 sshd: taro@pts/1 (sshd) 
    taro 27051 0.0 0.5 12628 2880 0 Ss 04:52 0:00.01 -sh (sh) 
    taro 27132 0.0 0.4 12324 2636 0 R+ 04:57 0:00.00 ps -aux
    
  4. 古いプロセスをkillします。
    kill 27050