Tem um script com perl shebang, precisa mudar para bash no meio

4

Eu tenho um script com dois blocos: O primeiro bloco é escrito em perl, o segundo bloco é escrito em bash

Como faço para alternar shells (perl - > bash) no meio do script? Script anexado abaixo:

#! /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
    
por user165209 18.09.2017 / 00:59

4 respostas

27

Apenas reescreva o loop em Perl:

for my $file (glob '*.mrc') {
    ( my $newname = $file ) =~ s/\..*/.mrc/;
    rename $file, $newname or warn "$file: $!";
}
    
por 18.09.2017 / 01:19
18

Ignorando o problema XY e para responder à pergunta no assunto, você poderia fazer:

#! /usr/bin/perl
":" || q@<<"=END_OF_PERL"@;

# perl code here

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

# bash code here

bash ignorará a parte até a linha =END_OF_PERL@ porque é:

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

enquanto a primeira linha, em perl, é apenas duas strings ( ":" e q@quoted-string@ ) ORed juntos, então um no-op.

=END_OF_PERL@ in perl é o início de uma seção pod (documentação) , ignorada por perl .

Observe que, se quiser passar variáveis de perl para bash , será necessário exportá-las para o ambiente (embora você também possa usar argumentos; aqui estamos encaminhando a lista de argumentos para o script perl recebido para o script bash ):

$ENV{ENV_VAR} = $perl_var;

O código assume que a parte perl não altera o diretório de trabalho atual, pois caso contrário, se $0 fosse um caminho relativo, ele se tornaria inválido quando passado para bash . Para contornar isso, você pode pegar um caminho absoluto do script no começo com:

#! /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

Esse é um exemplo de um script poliglota, um script que é um código válido em mais de um idioma. Se você gosta desses truques, pode dar uma olhada em aquele mais radical aqui ou que codegolf Q & A .

perldoc perlrun mostra outro exemplo de sh + perl poliglota, mas desta vez com sh sendo chamado primeiro (para sistemas que não suportam shebags).

    
por 18.09.2017 / 11:10
7

Você não pode alternar intérpretes, mas você pode gerar um novo processo shell e, em seguida, retornar ao perl:

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

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

Ou você pode substituir o processo perl atual por um shell

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

Mas, como o @choroba responde, o perl é uma linguagem de uso geral e pode lidar com praticamente qualquer tarefa.

    
por 18.09.2017 / 03:20
2

Você pode fazer tudo em Perl ou tudo em um script de shell. No entanto, misturar idiomas é um pouco confuso.

A versão do script de shell pode parecer algo como isso (com bash , por exemplo):

#!/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

Ou, um pouco mais idiomático (agora com% normalsh, mas usando um find que entende -execdir ):

#!/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

(ambos os exemplos são testados apenas no que diz respeito à geração de nome de arquivo)

Altere sh -c para sh -cx para obter um pouco de saída para cada arquivo renomeado ou adicione -print antes de -execdir .

Com -execdir , o comando shell fornecido será executado com o diretório do arquivo encontrado como seu diretório de trabalho. {} (e $1 dentro da subshell) será o nome base do arquivo encontrado.

Se tif2mrc precisar para ser executado dentro do diretório dos arquivos TIFF, altere o loop para

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

Observe que, em Perl, o uso de backticks para executar um comando shell retornará a saída desse comando. Você pode usar

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

se você não estiver interessado na saída de tif2mrc .

Além disso, é confuso ler um script ou programa que altera diretórios de um lado para outro. Além disso, pode levar a coisas indesejadas, se um diretório não existir (o chdir(".."); , então, levaria você a um nível de diretório muito alto).

Para fazer isso corretamente, execute o comando com um diretório de trabalho deslocado, como no meu último exemplo de shell, ou verifique corretamente o código de retorno do chdir() inicial em Perl (isso ainda levaria ao código que pode ser difícil de ler e seguir).

    
por 18.09.2017 / 09:54