web-dev-qa-db-ja.com

Perl Shebangを使用したスクリプトがあり、途中でbashに切り替える必要があります

2つのブロックを持つスクリプトがあります。最初のブロックはPerlで記述され、2番目のブロックはbashで記述されています。

スクリプトの途中でシェル(Perl-> bash)を切り替えるにはどうすればよいですか?以下に添付されているスクリプト:

#! /usr/bin/Perl -w
#
my @dirs = glob("*.frames");
foreach $dir (@dirs) {
   print "working on $dir\n";
   chdir $dir;
   my @digitfiles = glob ("RawImage_?.tif"); #need to make all files have 2-digit numbering for sort order to be correct
   foreach $file (@digitfiles) {
      my $newfile = $file;
      $newfile =~ s/RawImage_/RawImage_0/;
      rename $file,$newfile;
   }
   my $stackname = "../" . $dir . ".mrc";
   `tif2mrc -s *.tif $stackname`; #IMOD program to stack: -s option means treat input as signed INT
   chdir "../"; #go back up
}

#!/usr/bin/env bash
for f in *.mrc; do mv -- "$f" "${f%%.*}".mrc ; done
4
user165209

Perlでループを書き直すだけです。

for my $file (glob '*.mrc') {
    ( my $newname = $file ) =~ s/\..*/.mrc/;
    rename $file, $newname or warn "$file: $!";
}
27
choroba

XY問題 を無視し、件名の質問に答えるには、次のようにします。

#! /usr/bin/Perl
":" || q@<<"=END_OF_Perl"@;

# Perl code here

exec "bash", "--", $0, @ARGV;
=END_OF_Perl@

# bash code here

bashは、次の理由により、=END_OF_Perl@行までの部分を無視します。

: || something <<"=END_OF_Perl@"
...
=END_OF_Perl@

一方、Perlの最初の行は2つの文字列(":"および q@quoted-string@ )でORをとっただけなので、何もしません。

Perl=END_OF_Perl@は、 pod(ドキュメント) セクションの始まりであるため、Perlによって無視されます。

変数をPerlからbashに渡す場合は、それらを環境にエクスポートする必要があることに注意してください(ただし、引数を使用することもできます。ここでは、受け取った引数のリストをPerlスクリプトからbashスクリプトに転送します)。

$ENV{ENV_VAR} = $Perl_var;

このコードは、Perl部分が現在の作業ディレクトリを変更しないことを前提としています。そうでない場合、$0が相対パスである場合、bashに渡されると無効になります。これを回避するには、最初にスクリプトの絶対パスを使用できます。

#! /usr/bin/Perl
":" || q@<<"=END_OF_Perl"@;
use Cwd "fast_abs_path";
my $script_path = fast_abs_path $0;

# Perl code here

exec "bash", "--", $script_path, @ARGV;
=END_OF_Perl@

# bash code here

これは、複数言語の有効なコードであるポリグロットスクリプトの一例です。これらのトリックを楽しんでいる場合は、 より極端なもの ここまたは コードゴルフQ&A をご覧ください。

perldoc perlrunは、sh + Perl polyglotの別の例を示していますが、今回はshが最初に呼び出されます(she-bangsをサポートしないシステムの場合)。

18

switchインタープリターを使用することはできませんが、新しいシェルプロセスを生成してからPerlに戻ることができます。

my $sh_code = <<'END_SH'
for f in *.mrc; do mv -- "$f" "${f%%.*}".mrc ; done
END_SH

system 'bash', '-c', $sh_code

または、現在のPerlプロセスをシェルに置き換えることができます

exec 'bash', '-c', $sh_code

しかし、@ chorobaが答えるように、Perlは汎用言語であり、ほぼすべてのタスクを処理できます。

7
glenn jackman

すべてをPerlで実行することも、すべてをシェルスクリプトで実行することもできます。ただし、言語の混合は通常少し面倒です。

シェルスクリプトのバージョンは次のようになります次のようなものこれ(たとえば、bashを使用):

_#!/bin/bash

dirs=( *.frames )

for dir in "${dirs[@]}"; do
    printf 'Working on "%s"\n' "$dir"

    cd "$dir"

    digitfiles=( RawImage_?.tif )

    for file in "${digitfiles[@]}"; do
        newfile="RawImage_0${file#RawImage_}"
        mv "$file" "$newfile"
    done

    stackname="../$dir.mrc"
    tif2mrc -s *.tif "$stackname"

    cd ..
done

for f in *.mrc; do
    mv -- "$f" "${f%%.*}".mrc
done
_

または、もう少し慣用的です(現在はプレーンなshですが、_-execdir_を理解するfindを使用しています):

_#!/bin/sh

echo 'Renaming TIFF files'

find *.frames -type f -name 'RawImage_?.tif' \
    -execdir sh -c 'mv "$1" "RawImage_0${1#RawImage_}"' sh {} ';'

for d in *.frames; do
    printf 'Processing "%s"...\n' "$d"
    tif2mrc -s "$d"/*.tif "${d%%.*}.mrc"
done
_

(両方の例は、ファイル名の生成に関してのみテストされています)

_sh -c_を_sh -cx_に変更して、名前が変更されたファイルごとに少しの出力を取得するか、_-print_の前に_-execdir_を追加します。

_-execdir_を使用すると、指定されたShellコマンドが、見つかったファイルのディレクトリを作業ディレクトリとして実行されます。 _{}_(およびサブシェル内の_$1_)は、見つかったファイルのベース名になります。

_tif2mrc_ needsをTIFFファイルのディレクトリ内で実行する場合は、ループを次のように変更します。

_for d in *.frames; do
    printf 'Processing "%s"...\n' "$d"
    (cd "$d" && tif2mrc -s *.tif "../${d%%.*}.mrc" )
done
_

Perlでは、バッククォートを使用してシェルコマンドを実行すると、そのコマンドの出力が返されることに注意してください。代わりに使用できます

_system("tif2mrc -s *.tif $stackname");
_

_tif2mrc_の出力に興味がない場合。

また、ディレクトリを前後に変更するスクリプトやプログラムを読むのは混乱を招きます。さらに、ディレクトリが存在しない場合、不要な事態が発生する可能性があります(chdir("..");を使用すると、1つのディレクトリレベルが高くなりすぎます)。

これを適切に行うには、前回のシェルの例のように、置き換えられた作業ディレクトリでコマンドを実行するか、Perlの最初のchdir()のリターンコードを適切にチェックします(これにより、コードが生成される可能性があります)読みにくく、従うのが難しい)。

2
Kusalananda