低レベルperlスクリプトのススメ（その２）

![elf_and_student3a_result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/383429/a4d2539a-ebd6-99a5-8707-fc1d51a8b47e.jpeg)
**「これはね、テキストファイルを絶対文字化けしないようにする魔法のperlスクリプトだよ」**
**「路銀が尽きそうなので、つまらない魔導書買うのやめてもらっていいですか?」**

# はじめに

[Perl Advent Calendar 2023 13日目](https://qiita.com/doikoji/items/482e90b6b12a9713c418)の記事において、私はこう述べました。
> 私が考えるに、perlは低レベルスクリプトこそが命です。

> このような「低レベルスクリプト」を共有することも、非常に有効だと思います。皆さんも、手元にある同じようなスクリプトをぜひ紹介してください。

しかしながら、まだまだカレンダーは空白だらけ。よろしい、私がもう一度先陣を切ろうじゃないか。

# ボクの考えた最強の低レベルスクリプトたち

ここにあるのは、実際に私が自分の環境に置いているスクリプトばかりです。自家用に書いたものをほぼそのまま晒します。わざわざgithubに入れるほどでもない気がしますが、晒せば世界で3人ぐらいは役立ててくれる人が現れるのではないか? そんなスクリプト群です。作った年代もバラバラで、それゆえにスタイルもバラバラです。

そんなわけで、他の環境でちゃんと動くかは確認していません。が、まあだいたい大丈夫じゃないかな。たぶん。

## dos2unix （改行コードをlinux用に変換するスクリプト）

おそらくwindows環境で作られたテキストファイルの改行コードをlinuxで使えるように書き換えます。すなわち`\r`を消去します。元ファイル名の末尾に`.bak`を付加したファイル名でバックアップが保存されます。

### 使用例
```
$ dos2unix from_win.txt
```

### ソースコード

```
#!/usr/bin/perl -pi.bak
s/\r//

```

## v （とりあえずファイルの中身を確認するスクリプト）

ファイルの種類に依らず、とにかく内容をチェックしたいときに使うスクリプト。

fileコマンドを利用して対象ファイルの種別を判定し、それに適したプログラムを呼び出してファイルの中身をブラウズする仕組みです。たとえば.zipファイルなら`unzip -l`が実行されるしperlスクリプトなら`less -N`が実行されるしイメージファイルなら`eog`コマンドが呼び出されるといった具合。

私のubuntu環境に合わせたファイルタイプとツールの対応関係が%mime2progに書かれています。ここをユーザーそれぞれの環境に合わせて書き換えてから使います。

### 使用例
```
$ v targetfile
```

### ソースコード
```
#!/usr/bin/env perl

# simple versatile file viewer

# % v file

use strict;
my $infile=$ARGV[0];
if($infile eq ''){
  print STDERR <<'eot';
Usage:
% v [targetfile]
eot
exit();
}

my %mime2prog=(
  'image'      =>{'' => "eog"},
#  'image'      =>{'' => "gthumb"},
  'text'       =>{
                  ''       => \&vtxt,
                  'x-perl' => "less -N"
                 },
  'application'=>{
                  ''       => "file",
                  'pdf'    => "evince",
                  'x-gzip' => \&vgzip,
                  'zip'    => \&zip, #"unzip -l",
                  'gzip'   => \&vgzip,
                  'vnd.openxmlformats-officedocument.wordprocessingml.document' => '/usr/lib/libreoffice/program/oosplash --vi
ew',
                  'vnd.openxmlformats-officedocument.spreadsheetml.sheet'       => '/usr/lib/libreoffice/program/oosplash --vi
ew'
                 }
);

my $mime=`file -ibL $infile`;
my $type=`file -bL $infile`;
my($mime1,$mime2)=$mime=~m{^([^/]+)/([^;]+);};
if($mime1 eq 'inode' and $mime2 eq 'directory'){
  system("file $infile/*");
  exit();
}

my $prog=$mime2prog{$mime1}{$mime2};
(defined $prog) or $prog=$mime2prog{$mime1}{''};
if(ref($prog) eq 'CODE'){ # subroutin in this script
  $prog->($infile, $mime1, $mime2, $type);
}else{
  view("$prog $infile");
}

# .zip
sub zip{
  my($file, $mime1, $mime2, $type) = @_;
  if($file=~/\.(docx|xlsx)$/){
    view("/usr/lib/libreoffice/program/oosplash --view $file");
  }else{
    view("zless $file");
  }
}

# .gzip .bam
sub vgzip{
  my($file, $mime1, $mime2, $type) = @_;
  if($file=~/\.bam$/){
    view("samtools view $file|less");
  }elsif($file=~/\.(docx|xlsx)$/){
    view("/usr/lib/libreoffice/program/oosplash --view $file");
  }else{
    view("zless $file");
  }
}

my $prog=$mime2prog{$mime1}{$mime2};
(defined $prog) or $prog=$mime2prog{$mime1}{''};
if(ref($prog) eq 'CODE'){ # subroutin in this script
  $prog->($infile, $mime1, $mime2, $type);
}else{
  view("$prog $infile");
}

# .zip
sub zip{
  my($file, $mime1, $mime2, $type) = @_;
  if($file=~/\.(docx|xlsx)$/){
    view("/usr/lib/libreoffice/program/oosplash --view $file");
  }else{
    view("zless $file");
  }
}

# .gzip .bam
sub vgzip{
  my($file, $mime1, $mime2, $type) = @_;
  if($file=~/\.bam$/){
    view("samtools view $file|less");
  }elsif($file=~/\.(docx|xlsx)$/){
    view("/usr/lib/libreoffice/program/oosplash --view $file");
  }else{
    view("zless $file");
  }
}

my $prog=$mime2prog{$mime1}{$mime2};
(defined $prog) or $prog=$mime2prog{$mime1}{''};
if(ref($prog) eq 'CODE'){ # subroutin in this script
  $prog->($infile, $mime1, $mime2, $type);
}else{
  view("$prog $infile");
}

# .awk ...
sub vtxt{
  my($file, $mime1, $mime2, $type) = @_;
  if($type=~/\b(?:script|code)\b/){
    view("less -NX $file");
  }else{
    view("less -X $file");
  }
}

sub view{
  system($_[0]);
}

```

## xlsx2tsv.pl (エクセルのファイルをタブ区切りテキストファイルに変換）

エクセルで作られた.xlsxファイルからスプレッドシートを読み込み、タブ区切りテキストとして出力します。書き出し先のテキストファイルの名前は、元のファイルの拡張子を.xlsxから.tsvに置き換えたものとなります。

複数のスプレッドシートが含まれていたり、セルの結合が使われていたりした場合、どうなるかはチェックしていません。

### ソースコード

```
# from https://qiita.com/hiro_nico/items/fdc5ebb4a191643b09fa
use strict;
use warnings;
use Spreadsheet::ParseExcel;

my($infile, $outfile) = @ARGV;
unless($outfile){
  $outfile = $infile;
  $outfile=~s/\.xlsx$/.tsv/;
}
my $parser   = Spreadsheet::ParseExcel->new();
print STDERR "Trying to read $infile\n";
my $workbook = $parser->parse($infile);

if ( !defined $workbook ) {
    die $parser->error(), ".\n";
}
open(my $fho, '>:utf8', $outfile) or die "Cannot modify $outfile";

for my $worksheet ( $workbook->worksheets() ) {

    my ( $row_min, $row_max ) = $worksheet->row_range();
    my ( $col_min, $col_max ) = $worksheet->col_range();

    for my $row ( $row_min .. $row_max ) {
        for my $col ( $col_min .. $col_max ) {

            my $cell = $worksheet->get_cell( $row, $col );
            next unless $cell;

            print STDERR "Row, Col    = ($row, $col)\n";
            print {$fho} "Value       = ", $cell->value(),       "\n";
            print {$fho} "Unformatted = ", $cell->unformatted(), "\n";
            print {$fho} "\n";
        }
    }
}
```

# おわりに

**特に総括することもありません。** でも、みなさんもっとカジュアルにいろいろ晒しあってもいいんじゃないかな。というわけで皆さんの低レベルスクリプトも是非見せてください。