The macosxhints Forums

The macosxhints Forums (http://hintsforums.macworld.com/index.php)
-   UNIX - General (http://hintsforums.macworld.com/forumdisplay.php?f=16)
-   -   get the value of a variable in a previous command, single line (http://hintsforums.macworld.com/showthread.php?t=106596)

eitchbar 10-23-2009 07:43 AM

get the value of a variable in a previous command, single line
 
I'm using a program that writes postscript files (it's abcmp2s, which formats music, but that isn't relevant). I want to write the file, then open the file in Preview.

With this particular program, it happens there's a work-around that lets me pipe standard input to 'open'… but it isn't ideal for a number of reasons, primarily that the postscript data goes straight to 'open' without being saved to a ps file first (so Preview opens a pdf translation of a temporary ps… if I want to do anything with the ps, I have to rummage around in tmp, or repeat the output without the pipe).

Is there a way to say "the file that was created by the previous command"? Or a way to say "the value of this particular parameter of the previous command"?

I'd like to say
Code:

thisprogram … | open [the file I just made] -a Preview
or, because the output destination is set by -O,
Code:

thisprogram … -O [output destination] | open [output destination] -a Preview
where I don't have to manually enter the file to be opened a second time. (now that I see it, I suppose it'd actually be ';' not '|' right?)

thanks for the help

eitchbar 10-23-2009 07:52 AM

oh, or can I create variables with something like?
Code:

thisprogram … -O {thethingtoopen}[output destination] ; open $thethingtoopen -a Preview
just rattling off the methods I'd try if I knew how :)

hayne 10-23-2009 11:28 AM

Usually you'd do this sort of thing by assigning the filename to a variable first, then use that variable in the two places.
Code:

destFile="MyStuff/Docs/foo.ps"
thisProgram ... -O $destFile
open $destFile


Las_Vegas 10-23-2009 11:47 AM

Why not simply use a folder action to open any new/updated file in preview?

eitchbar 10-23-2009 03:56 PM

Vegas— The destination folder changes with the input file (but isn't nec. in the same path as the input file). Along the same lines, in the past I've repeatedly output to some particular file, overwriting whatever already there; I put that file in the dock, and Previewing the latest output was always one click away. Both that and folder actions are okay but not part of the workflow I'd like.

Hayne— cool, piping that does it. but…

Manually entering the file name again is still quicker than setting the variable and referencing it twice. There's no simple way to say?
Code:

| getfrompreviouscommand value(-O)
Can I pipe the full text of the previous command to grep? Then I could write 'prev'
Code:

destfile=[grep the word following -O in <text of prev command>]
open $destfile

and run 'thisprogram … | prev'

(Maybe I just need to ask the author to build in a -p option, since he obviously has access to the output file's name. But where's the fun in that? ;)

hayne 10-23-2009 04:56 PM

Quote:

Originally Posted by eitchbar (Post 558545)
Manually entering the file name again is still quicker than setting the variable and referencing it twice.

I assumed you were doing this in a script of some sort where the filename would be hard-coded, or perhaps obtained via a command-line parameter to the script. ($1 etc)

Or you could make an alias or Bash function to do this - see the section on shell aliases in this Unix FAQ

The arguments passed to the previous command are accessible via the Bash shell's history mechanism. E.g. the last argument of the previous command is $!

eitchbar 10-24-2009 12:21 AM

Right… maybe I was wrong to think this belonged in Newcomers. I thought it would be a simple, "Yes, you're clearly new to unix: it's 'getlastvalueof -O'" or "here, I wrote this 'bobsGetLastValueOf'". I've glanced at your FAQ before, Hayne, and it really looks like a great resource… some day I'll print it out, get some OS X Unix books from the library, and learn it. But for now:

It's not in a script; it's a one-off command 'abcm2ps <inputfile.abc> -O <outputfile.ps>'. I won't bore you with the reasons, but the goal is to have the whole "export, then preview what was exported" command on one line (';'s are ok) without using any user-defined functions.

Having it all on one line, I can't do '… -O output.ps; open -a Preview !!:$' Can I get '!#<second to last word>'?

eitchbar 10-24-2009 05:09 AM

get the value of a variable in a previous command, single line
 
I'm using a command that creates a PostScript file (details irrelevant, though I can go into if anyone wants), and I want to follow it with a command to open that newly-created file in Preview. The destination of the first command's output is specified by its -O option. If I had two lines to work with, it'd be easy enough to always put -O at the end, and follow it with 'open -a Preview !!:$'… but I'm restricted to a single line (again, reasons irrelevant).

So far I'm running
Code:

postscriptExporter -O <output.ps> … ; open -a Preview !#:2
which works fine as long as -O is the first thing to follow the command name. Fine; not elegant… I'd like
1) grep !# for the word following -O, or
2) take the first word after -O in !# (or the second word of everything following -O in !#)
all right there in the command, no shell function use, one line. Can I do that?

On a related note, has anyone written a shell function to answer questions of the form "Last time I ran 'open', what application did I open with?" — that returns the value of a certain parameter from the last time you set it in a certain command? I'm imagining some sort of
Code:

(!<commandName> AND !$<paramName>)
[the command from Question 1]

both of those are beyond my bash history abilities

hayne 10-24-2009 05:12 AM

eitchbar:
I have merged your new thread with the existing one on the same subject. Please don't start new threads when there is an existing one on the same (or similar) subject. If you think your thread is in the wrong forum section or titled incorrectly, contact one of the moderators and ask to have it moved/fixed.

eitchbar 10-24-2009 05:16 AM

Hayne, you're just to fast for me! :o

hayne 10-24-2009 05:17 AM

Quote:

Originally Posted by eitchbar (Post 558635)
If I had two lines to work with, it'd be easy enough to always put -O at the end, and follow it with 'open -a Preview !!:$'… but I'm restricted to a single line (again, reasons irrelevant).

Please explain the reasons.
The context of use is far from irrelevant - it is often the most important part of a query.

And why can't you use the $! mechanism I suggested earlier?
Maybe show us a specific example, in full.

And why can't you make use of shell conveniences like aliases & functions?

eitchbar 10-24-2009 06:41 AM

Firstly, remember the key questions here are in post 8, not those before. (it's not at all clear to me that posts 1-7, merged here from a separate thread, don't address a question (cf. the first bit of code in post 8) quite different from the one I'm asking in this thread)

so… okay Hayne I can answer those questions… but really, the reasons have nothing to do with the command itself other than that they restrict it to one line, no new functions or aliases — they're all supra-terminal context:

1) those are the terms in which I'm defining the problem in Post 1-7 and Post 8 Part 1 (see also reason 5, below)

2) I'd like to be able to share this with friends of mine who use this exporter and who would be intimidated if not completely confused by bash profile tweaks, or temporary function and alias creation. 3) The word processor application I use to write and edit these files (analogous to your favorite TeX word processor) has an "export to ps" feature (analogous to publishing a TeX file), which pops up a window full of check- and text-boxes, and it has a little window showing the unix command that'll be run. You can manually edit the command — so you can paste in a "and open it up" snippet at the end, if you have such a snippet. Those same friends are much more comfortable exporting through this application's ui than at the command line. And the application will only send a single line. 4) I myself do this exporting at the command line, doing dozens of export-open repetitions in fairly quick succession as I tweak the formatting file (again, the TeX analogy). The command looks like 'theExporter <input> -f <format file> -e <range of line to export> -O <output.ps>', and I typically only change the export name — and up-arrow (I'm not embarrassed), change out1.ps to out2.ps, and double-click the icon in the neighboring Finder window is as fast as anything else I could do. Unless I could knock out the double-clicking (and in Post 8 I do just that— but with inelegant restraints on the order of terms in the export command)

5) It'd be cool, I'm new to manipulating the bash history, I imagine the second part of my question would be a useful function to have, it's the whole problem I want to tackle, and it's got to be possible

If you see a way, within the limits intergral to the problem, to use a $! method to tackle Post 8, especially the more general part 2 (or even posts 1-7, in a more elegant way than I have in Post 8) awesome, please tell me about it. (if you've already explained, it was over my head.) all I can imagine is in-line variable declaration, of the sort 'theExporter <input> -O {what will now be called TheOutput<output.ps>}'… I wouldn't be too surprised if there was a way to do that, but I don't know how and would appreciate advice from anyone who does (though, again, I don't how it bears on the questions linked at the start of this post). (it would be sweet if this post didn't kill my chances of getting help on Post 8, but I wouldn't be too surprised by that either)

hayne 10-24-2009 04:39 PM

I don't see any way to do it simply by adding stuff on that command-line. The problem is that Bash does the history expansion on the whole line before breaking it up into separate commands by looking at the semi-colons. So history-based stuff won't work.

I think the simplest way to get the functionality you want is to replace the current exporter command with a wrapper script that adds the "open this file" functionality.
Here's one implementation of a wrapper script (in Perl):
Code:

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;

my $exporterCmd = "exporter";
my $openCmd = "open";

# Extract the filename from the command-line's "-O" option
# and check if the "--open" option was used
my $filename;
my $openDesired = 0;
Getopt::Long::Configure("pass_through");
GetOptions(
          'O=s'    => \$filename,
          'open'  => \$openDesired,
          );
die "No filename was specified via -O\n" unless $filename;

# Run the exporter command
my $otherArgs = join(' ', @ARGV);
my $exporterCommandLine = "$exporterCmd $otherArgs -O $filename";
print "About to run command $exporterCommandLine\n";
system($exporterCommandLine);

# Open the file if the "--open" option was used
if ($openDesired)
{
    my $openCommandLine = "$openCmd $filename";
    print "About to run command $openCommandLine\n";
    system($openCommandLine);
}

To use this, you would first rename the existing exporter program to something different. (E.g. if the existing exporter program is named "theExporter", go to the folder where it is and rename it to "theExporter_original" or something.)
Then save the Perl script I supplied above in a file with the same name as the existing exporter program and edit it to make the variable $exporterCmd refer to the actual name of the existing exporter program - you would use a plain text editor to do this (e.g. TextWrangler).
Then make that file executable via the command:
chmod +x theExporter
(using the actual name of the file instead of "theExporter")
and then move it to the folder where the existing (now renamed) exporter program is.

After all that (which could be done by an installer script for use by your friends),
all you'd need to do would be to add the "--open" option in the command-line presented by your GUI editor.
It would run the wrapper script and the wrapper script would invoke the original exporter and then open the file using the 'open' command.

eitchbar 10-25-2009 03:03 AM

Quote:

Originally Posted by hayne (Post 558704)
The problem is that Bash does the history expansion on the whole line before breaking it up into separate commands by looking at the semi-colons. So history-based stuff won't work.

right, that's why I was stuck using !#, which left me with "… ; open !#:2" and requiring that O was the first option set

Quote:

Originally Posted by hayne
the code

That wrapper command is great! Thanks, I didn't know how to do that. After looking through the GetOptions documentation I'm not sure exactly how you cut 'open' out of @ARGV after reading it. Was it by using "'open' => …" instead of "'open…' => …"?

The exporter allows '… <input.abc> -O =', which outputs a .ps with the same basename and in the same directory as the abc file (really, ".abc" - cute, no?). I hadn't used Perl until today: How can I get the part of the argument that isn't options? The order is flexible, so I can't $ARGV[n]. Do I have to use GetOptions to cut out everything that is an option?

Once I get it, the renaming is fine:
Code:

…as prev post…
use File::Basename;
my $inFile;
my $outFile;
my $openCmd = …

GetOptions(…gets outFile, as per script in prev post…
if ($openDesired)
{
        if ($outFile eq "=")
                {
                my ($inBase, $inDir, $inSuff) = fileparse($inFile, ".abc");
                $outFile = "$inDir$inBase.ps";
                }
        my $openCommandLine = "$openCmd $outFile";
        print "$openCommandLine\n";
        system($openCommandLine);
}

but that's useless without an $inFile to work with

thanks!

hayne 10-25-2009 12:04 PM

Quote:

Originally Posted by eitchbar (Post 558753)
I'm not sure exactly how you cut 'open' out of @ARGV after reading it. Was it by using "'open' => …" instead of "'open…' => …"?

I'm not sure I understand the question.
GetOptions takes a list of option names (before the arrows) and the variables where the value of those options are to be put (after the arrows). For each option, you can specify if the option takes a value and what type of value that is, by supplying a letter after an '='. E.g. 'O=s' means that the "-O" option takes a string value.

Quote:

The exporter allows '… <input.abc> -O =', which outputs a .ps with the same basename and in the same directory as the abc file (really, ".abc" - cute, no?). I hadn't used Perl until today: How can I get the part of the argument that isn't options? The order is flexible, so I can't $ARGV[n]. Do I have to use GetOptions to cut out everything that is an option?
I presume that the input file is the first command-line argument that isn't preceded by an option name.
Since I'm using the 'pass_through' in the configuration of GetOptions above, anything that it doesn't recognize as an option remains in the @ARGV array.
So the name of the input file is there along with all the remaining options.
If it were always the first thing, you could get it simply as $ARGV[0], but you say that it isn't necessarily the first.

One way to go (and maybe the best, if the most work) would be to add the names of all of the supported options in the call to GetOptions and then reproduce them in the call to the original exporter command.

Another (easier) possibility would be to just loop through the @ARGV array yourself (in the Perl script), checking each element to see if it starts with "-" and hence find the input filename (presuming that it is the first command-line argument that isn't an option). Of course you would need to know which of the options take a value and skip over the values as well.
Start with a loop like this:
Code:

foreach my $arg (@ARGV)
{
    if ($arg =~ /^-/)  # starts with -
    {
        print "skipping arg $arg\n";
    }
    else
    {
        print "Arg: $arg\n";
    }
}

and run the script so you can see what you are faced with.

eitchbar 10-26-2009 08:00 AM

Quote:

Originally Posted by hayne (Post 558780)
I'm not sure I understand the question.
GetOptions takes a list of option names (before the arrows) and the variables where the value of those options are to be put (after the arrows). For each option, you can specify if the option takes a value and what type of value that is, by supplying a letter after an '='. E.g. 'O=s' means that the "-O" option takes a string value.

You're right, I wasn't clear. Doesn't matter — I'd misunderstood part of GetOptions, and have realized my mistake. Specifically, I someone hadn't noticed that anything called by GetOptions was taken out of @ARGV (I'd overlooked your adding that back in in your 'exporterCommandLine'

Quote:

Originally Posted by hayne (Post 558780)
One way to go would be to add the names of all of the supported options in the call to GetOptions and then reproduce them in the call to the original exporter command…another possibility would be to just loop through the @ARGV array yourself (in the Perl script), checking each element to see if it starts with "-"

The second idea's great: since the exporter command requires a .abc document as input, and no other options take a .abc as their value, I just do
Code:

foreach my $arg (@ARGV)
{
    if ($arg =~ /.abc/)  # ends with abc
    {
        $inFile = $arg;
        print "Input file expanded to $inFile\n";
    }
}
if ($preview)
    die "Preview failed: Input file must be .abc\n" unless $inFile;
                if ($outFile eq "=") {

Success! Thanks, I've learned a lot

As for the just-for-kicks project: Looks like I have enough to write the command that'll answer to "Tell me the value of the option -x in the most recent 'y'" — I'll work on that tomorrow and post what I come up with to see how my solution looks to more expert eyes

bb5ch39t 10-26-2009 10:42 AM

What about using "tee"?

thisprogram | tee output.file | nextprogram

"thisprogram" writes its output to stdout and pipes it to "tee". "tee" reads stdin and writes it to both "output.file" and stdout, piping stdout to "nextprogram".

hayne 10-26-2009 10:55 AM

Quote:

Originally Posted by eitchbar (Post 558887)
Code:

    if ($arg =~ /.abc/)  # ends with abc


The above tests if the string "abc" occurs anywhere in $arg after the first character. So it's not quite what you want. (E.g. it would match the string "crabcatcher".)

The dot (.) is a special character in regular expressions (which is what Perl is looking for in the above construction). It matches any one character.
You want a literal dot, so you need to escape it with a backslash: \.

And you only want to match ".abc" at the end of the string, so you need to "anchor" it with a "$":

Code:

    if ($arg =~ /\.abc$/)  # ends with .abc
Glad you've got it working - you're a fast learner.

eitchbar 10-26-2009 10:15 PM

Quote:

Originally Posted by hayne (Post 558903)
~ /\.abc$/)

Oh geez, being new to Perl is hardly an excuse for my missing that. That's what I get for staying up too late. Makes you glad I'm not doing this professionally, doesn't it?

Quote:

Originally Posted by hayne (Post 558903)
you're a fast learner.

The secret is joblessness — you can spend many many hours on this stuff when you have few obligations :o

Quote:

Originally Posted by bb5ch39t (Post 558901)
What about using "tee"?

Cool, tee's good to know about. In this case, even though I imagine I could shorten the script, I like opening the actual file: opening from stinput opens a file with a non-English title (in Preview it's 50+ characters, some translation of the start of the stinput I suppose)

Here's my "What was the last time this was done?" script, used
historical <command> prints the bash !<command>
historical <command> -o <option> prints the equivalent of bash "!<command>" which is also "!? -<option>?"
historical <command> -O <option> -o, but prints the value of <option>
-f and -F specify a history file.

-O is broken, giving 'Error in option spec: "$saughtOption=s"' on the second GetOptions call, making this all a lot of unnecessary fanciness :(

Any comments? (Besides chuckling at all my IFs - I can't help it, as a kid I cut my teeth on TI-BASIC. I assume I'm not close to taking advantage of Perl)

Code:

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long qw(:config no_ignore_case);

my $historicalUse;
my $historicalValue;
my $saughtOption;
my $onlyOption;
my $historyFile = ".bash_history"; # default history file

GetOptions(
        'o=s'        => \$saughtOption,        # -o specifies an option
        'O=s'        => \$onlyOption,        # -O "Only return Option's value"
        'f=s'        => \$historyFile,        # -f or -F overrides default history file
        'F=s'        => \$historyFile,
        );

# pass -O value on to erstwhile -o value
if( $onlyOption ){
        $saughtOption = $onlyOption;
}

my $saughtCommand = join('',@ARGV);
die "No command specified.\n" unless $saughtCommand;

# read the history file
open(HISTFILE, $historyFile) or die "Could not open history file $historyFile\n";
my @historyList = <HISTFILE>;
close(HISTFILE);

# find the command or command+option in the history file
foreach my $historyLine (@historyList) {
        if($saughtOption){
                if($historyLine =~ /^$saughtCommand/ && $historyLine =~ /\ -$saughtOption/){
                        $historicalUse = $historyLine;
                }
        } else {
                if($historyLine =~ /^$saughtCommand/){
                        $historicalUse = $historyLine;
                }
        }
}
# end here to use $historicalUse somewhere else

# print historical value
if($onlyOption && $historicalUse) {
                @ARGV = $historicalUse;
                GetOptions(
                        '$saughtOption=s'        => \$historicalValue
                );
# end here to use $historicalValue somewhere else
                die "$historicalValue\n";
        }

# print historical command, or "not found"
if($saughtOption){
        die "No \'$saughtCommand\' with option -$saughtOption found in \'$historyFile\'.\n"
        unless $historicalUse;
} else {
        die "No $saughtCommand command found in history $historyFile.\n" unless $historicalUse;
}
print "$historicalUse";


hayne 10-26-2009 10:56 PM

Perl doesn't expand/interpolate variables inside single quotes, so:
'$saughtOption=s'
is taken literally - no variable interpolation.

eitchbar 10-27-2009 04:04 AM

Hmm… that doesn't fix it. In the example
Code:

open -a Preview
historical open -o a

replacing '$saughtOption=s' with 'a=s' leaves $historicalValue unitialized when it's referred to on the next line. Thought first it was the trailing newline on @ARGV, but chomping $historicalValue doesn't help the GetOptions.
Code:

my $historicalValue;

chomp($historicalUse);

if($onlyOption && $historicalUse) {
        @ARGV = $historicalUse;
#die "a\n@ARGV\nb\n"; #uncommenting confirms @ARGV is 'open -a Preview'
        GetOptions(
                'a=s'        => \$historicalValue
        );
        die "val: $historicalValue\n"; #error! $historicalValue unitialized!
}

Whatever, I'm done. It was fun, but now I'm just wasting time. Thanks to everyone for the ideas, especially Hayne- really nice of you take the time be my advisor

hayne 10-27-2009 05:07 AM

Quote:

Originally Posted by eitchbar (Post 559028)
Code:

#die "a\n@ARGV\nb\n"; #uncommenting confirms @ARGV is 'open -a Preview'
        GetOptions(
                'a=s'        => \$historicalValue
        );


Note that Perl's @ARGV does not (unlike argv in C++) have the name of the program in the zeroth position ($ARGV[0]).
So that "open" is misplaced in @ARGV.

But I don't see why that would prevent GetOptions from working.

Note also that it is (IMHO) slightly strange to be assigning a scalar to an array with a statement like:
@ARGV = $historicalUse;
I would either use parentheses: @ARGV = ($historicalUse);
or start with an empty array @ARGV=(); and then add the scalar with 'push': push(@ARGV, $historicalUse);
But I guess it works, so it's just a matter of style.

ganbustein 10-27-2009 07:35 AM

Quote:

Originally Posted by hayne (Post 559033)
Note also that it is (IMHO) slightly strange to be assigning a scalar to an array with a statement like:
@ARGV = $historicalUse;
I would either use parentheses: @ARGV = ($historicalUse);
or start with an empty array @ARGV=(); and then add the scalar with 'push': push(@ARGV, $historicalUse);
But I guess it works, so it's just a matter of style.

It works because it's the same thing. It's a common misconception that parentheses create lists in Perl. In most cases, they're only for overriding operator precedence. For example, because comma has a lower precedence than assignment, the statement
@a = $x, $y
is parsed as
(@a = $x), $y;
which is probably not what you want (and will give you a warning if you use warnings;). The warning is that $y is a useless evaluation in a void context, because it has no side-effects. Change it to have a side-effect, and the warning disappears:
@a = $x, ++$y;
parses as:
(@a = $x), (++$y);
Adding parentheses, as in:
@a = ($x, $y);
serves to change the grouping, but does not change the scalar-or-list nature of the assignment. What makes this a list assignment is the presence of a list on the left hand side of the assignment.

These statements:
@a = $x;
@a = ($x);
@a = (($x));
@a = ((($x)));
are all parsed identically except for the varying numbers of superfluous parentheses. In all these cases, the right hand side is evaluated in list context because the left hand side is a list.

A comma has different meanings in different contexts, so that in:
($x, $y) = @a;
it's the comma on the left hand side of an assignment that creates the list. The parentheses are there only to make the comma bind tighter than the assignment.

Parentheses are useful for delimiting an empty list, but they don't create one. You have to already be in list context for some other reason:
@a = ; # syntax error
@a = (); # assign an empty list
$x = @a; # assign 0, the length of the right hand side
$x = (); # assign nothing, not even an empty list, leaving $x uninitialized
@a = (7,8); # assign 2-element list
$x = @a; # assign 2, the length of the right hand side
$x = (7,8); # discard 7 (and warn), assign 8, nary a list in sight
In Perl, context is everything. The very meaning of an expression like "(7,8)" changes depending on its context. Learn to "follow the context", and Perl becomes less cryptic.


All times are GMT -5. The time now is 05:52 PM.

Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2014, vBulletin Solutions, Inc.
Site design © IDG Consumer & SMB; individuals retain copyright of their postings
but consent to the possible use of their material in other areas of IDG Consumer & SMB.