I don't have much experience with perl, and would appreciate any/all feedback....
[Before I start: I do not have access/authority to change the existing perl scripts.]
I run a couple perl scripts several times a day, but I would like to begin capturing their output in a file.
The first perl script does not take any arguments, and I'm able to "tee" its output without issue:
/asdf/loc1/rebuild-stuff.pl 2>&1 | tee $mytmpfile1
The second perl script hangs with this command:
/asdf/loc1/create-site.pl --record=${newsite} 2>&1 | tee $mytmpfile2
FYI, the following command does NOT hang:
/asdf/loc1/create-site.pl --record=${newsite} 2>&1
I'm wondering if /asdf/loc1/create-site.pl is trying to process the | tee $mytmpfile2 as additional command-line arguments? I'm not permitted to share the entire script, but here's the beginning of its main routine:
...
my $fullpath = $0;
$0 =~ s%.*/%%;
# Parse command-line options.
...
Getopt::Long::config ('no_ignore_case','bundling');
GetOptions ('h|help' => \$help,
'n|dry-run|just-print' => \$preview,
'q|quiet|no-mail' => \$quiet,
'r|record=s' => \$record,
'V|noverify' => \$skipverify,
'v|version' => \$version) or exit 1;
...
Does the above code provide any clues? Other than modifying the script, do you have any tips for allowing me to capture its output in a file?
It's not hanging. You are "suffering from buffering". Like most programs, Perl's STDOUT is buffered by default. Like most programs, Perl's STDOUT is flushed by a newline when connected to a terminal, and block buffered otherwise. When STDOUT isn't connected to a terminal, you won't get any output until 4 KiB or 8 KiB of output is accumulated (depending on your version of Perl) or the program exits.
You could add $| = 1; to the script to disable buffering for STDOUT. If your program ends with a true value or exits using exit, you can do that without changing the .pl file. Simply use the following wrapper:
perl -e'
$| = 1;
$0 = shift;
do($0);
my $e = $# || $! || "$0 didn\x27t return a true value\n";
die($e) if $e;
' -- prog args | ...
Or you could fool the program into thinking it's connected to a terminal using unbuffer.
unbuffer prog args | ...
I am launching a bunch of the same script (generate_records.php) into screens. I am doing this to easily parallelize the processes. I would like to write the output of each of the PHP processes to a log file using something like &> log_$i (StdOut an StdErr).
My shell scripting is weak sauce, and I can't get the syntax correct. I keep getting the output of the screen, which is empty.
Exmaple: launch_processes_in_screens.sh
max_record_id=300000000
# number of parallel processors to run
total_processors=10
# max staging companies per processor
(( num_records_per_processor = $max_record_id / $total_processors))
i=0
while [ $i -lt $total_processors ]
do
(( starting_id = $i * $num_records_per_processor + 1 ))
(( ending_id = $starting_id + $num_records_per_processor - 1 ))
printf "\n - Starting processor #%s starting at ID:%s and ending at ID: %s" "$i" "$starting_id" "$ending_id"
screen -d -m -S "process_$i" php generate_records.php "$starting_id" "$num_records_per_processor" "FALSE"
((i++))
done
If the only reason you're using screen is to launch many processes in parallel, you can avoid it entirely and use & to start them in the background:
php generate_records.php "$starting_id" "$num_records_per_processor" FALSE &
You may also be able to remove some code by using parallel.
I have a perl script running as root, and from within it I want to execute a system command bar as a lesser priveleged user foo. So I have my system call wrapped as follows:
sub dosys
{
system(#_) == 0
or die "system #_ failed: $?";
}
And so I want to say:
as user foo dosys("bar")
Is there a mechanism within perl or the underlying bash shell that I can use to do this? (I would prefer one that didn't require installing an additional cpan library if possible)
The POSIX module is a Perl core module, and it includes the functions:
setuid()
setgid()
and related get*id() functions, though the values are also available through special variables:
$) and $( (effective and real GID)
$< and $> (effective and real UID)
You can also try setting those directly (per $EGID and $UID).
system('su www-data -c whoami')
> www-data
You have to change groups first, remember to quash supplementary groups, and then change user. You'll want to do this in a separate process, so that the [UG]ID changing doesn't affect privs on your root process.
sub su_system {
my $acct = shift;
my $gid = getgrnam $acct; # XXX error checking!
my $uid = getpwnam $acct;
if (fork) { # XXX error checking!
wait;
return $? >> 8;
}
# -- child
$( = $) = "$gid $gid"; # No supp. groups; see perlvar $)
$< = $> = $uid;
exec #_; # XXX not as safe as exec {prog} #argv
# oh, and what if $acct had [ug]id zero? darn
}
Proceed with caution.
Situation: I'm using multiple monitors and I want to get their names in bash. Currently I'm using Ubuntu 10.04.
I know about xrandr. From it I can get only statistics data. What I want is to read all monitor names in an array to work with them.
Is there a clear way to do that without cutting names from some kind of string? A clear way would be reading them from file. A not clear way would be to pipe xrandr output to some sort a function to cut names out from it.
Inspired by Beni's answer, this will read the EDID data using xrandr and extract the monitors names according to the EDID specification, with no need of any external tools like parse-edid:
#!/bin/bash
while read -r output hex conn; do
[[ -z "$conn" ]] && conn=${output%%-*}
echo "# $output $conn $(xxd -r -p <<< "$hex")"
done < <(xrandr --prop | awk '
!/^[ \t]/ {
if (output && hex) print output, hex, conn
output=$1
hex=""
}
/ConnectorType:/ {conn=$2}
/[:.]/ && h {
sub(/.*000000fc00/, "", hex)
hex = substr(hex, 0, 26) "0a"
sub(/0a.*/, "", hex)
h=0
}
h {sub(/[ \t]+/, ""); hex = hex $0}
/EDID.*:/ {h=1}
END {if (output && hex) print output, hex, conn}
' | sort
)
Uses awk to precisely extract the monitor name only, and no extra garbage from the EDID, hence "magic numbers" like 000000fc00, 26 and 0a. Finally uses xxd to convert from hex to ASCII, printing one monitor name per line.
Based on this solution I made a handy script to switch monitors, which can also be used to simply list monitor info:
$ monitor-switch --list
Connected monitors:
# DFP5 HDMI HT-R391
# DFP7 DVI-I DELL U2412M
$ monitor-switch --list
Connected monitors:
# DisplayPort-1 DisplayPort DELL U2412M
# DisplayPort-3 DisplayPort DELL U2415
# HDMI-A-2 HDMI LG TV
Tested on Ubuntu 16.04, 18.04. (I know its too late to answer but this solution is relevant today)
$ sudo apt-get install -y hwinfo
...
$ hwinfo --monitor --short
monitor:
SONY TV
AUO LCD Monitor
I have two monitors attached. One with the laptop and the other is an external display. As soon as the external monitor is plugged-in or out, this command reflects the change. You continuously need to poll. Removing the --short option gives more detailed information.
You can poll the state with the following background job:
$ while true;
> do
> hwinfo --monitor --short;
> sleep 2;
> done >> monitor.log &
The while true loop runs infinite times. The sleep 2 pauses each iteration of the loop for 2 seconds. And the output of hwinfo --monitor --short is appended to monitor.log. This log file can give you the activity history of monitor plug-in and plug-out.
FYI: I am using a background (daemon) python script using the above command (and other similar ones) to detect if someone is doing some HW plug-ins and plug-outs with the systems in the computer lab. If so, I get appropriate notifications that someone plugged-out/in a monitor, mouse or keyboard in almost real-time!
More info about hwinfo command is here. Its man page is also a good source.
sudo get-edid didn't work for me. (EDIT: now works on another computer, Lubuntu 14.10; I'd blame BIOS differences but that's a random guess...)
Anyway under X, xrandr --verbose prints the EDID block. Here is a quick and dirty way to extract it and pass to parse-edid:
#!/bin/bash
xrandr --verbose | perl -ne '
if ((/EDID(_DATA)?:/.../:/) && !/:/) {
s/^\s+//;
chomp;
$hex .= $_;
} elsif ($hex) {
# Use "|strings" if you dont have read-edid package installed
# and just want to see (or grep) the human-readable parts.
open FH, "|parse-edid";
print FH pack("H*", $hex);
$hex = "";
}'
If you don't want to parse xrandr output, write a C program using libXrandr that gets only what you want. If all you want to do is to query information, it can be done quickly. Read this document.
If you want to get the real monitor name, an alternative to #dtmilano's solution is to get the EDID property of the monitor using libXrandr and then manually parse it and print (read the EDID specification).
xrandr source code.
I know this is a dirty way, but it gives me some monitor model name even better than sudo get-edid|parse-edid. It reads information in arrays, and outputs it in a way that can be read like you would read a file. You may modify it according to your needs.
#!/bin/bash
#
#
# get-monitors.sh
#
# Get monitor name and some other properties of connected monitors
# by investigating the output of xrandr command and EDID data
# provided by it.
#
# Copyright (C) 2015,2016 Jarno Suni <8#iki.fi>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. See <http://www.gnu.org/licenses/gpl.html>
set -o nounset
set -o errexit
# EDID format:
# http://en.wikipedia.org/wiki/Extended_Display_Identification_Data#EDID_1.3_data_format
# http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf
declare -r us=';' # separator string;
# If EDID has more than one field with same tag, concatenate them,
# but add this string in between.
declare -r fs=$'\x1f' # Field separator for internal use;
# must be a character that does not occur in data fields.
declare -r invalid_edid_tag='--bad EDID--'
# If base EDID is invalid, don't try to extract information from it,
# but assign this string to the fields.
# Get information in these arrays:
declare -a outs # Output names
declare -a conns # Connection type names (if available)
declare -a names # Monitor names (but empty for some laptop displays)
declare -a datas # Extra data; may include laptop display brand name
# and model name
declare -i no # number of connected outputs (to be counted)
# xrandr command to use as a source of information:
declare -r xrandr_output_cmd="xrandr --prop"
hex_to_ascii() {
echo -n "$1" | xxd -r -p
}
ascii_to_hex() {
echo -n "$1" | xxd -p
}
get_info() {
no=0
declare OIFS=$IFS;
IFS=$fs
while read -r output conn hexn hexd; do
outs[no]="${output}"
conns[no]="${conn}"
names[no]="$(hex_to_ascii "$hexn")"
datas[no]="$(hex_to_ascii "$hexd")"
(( ++no ))
done < <(eval $xrandr_output_cmd | gawk -v gfs="$fs" '
function print_fields() {
print output, conn, hexn, hexd
conn=""; hexn=""; hexd=""
}
function append_hex_field(src_hex,position,app_hex, n) {
n=substr(src_hex,position+10,26)
sub(/0a.*/, "", n)
# EDID specification says field ends by 0x0a
# (\n), if it is shorter than 13 bytes.
#sub(/(20)+$/, "", n)
# strip whitespace at the end of ascii string
if (n && app_hex) return app_hex sp n
else return app_hex n
}
function get_hex_edid( hex) {
getline
while (/^[ \t]*[[:xdigit:]]+$/) {
sub(/[ \t]*/, "")
hex = hex $0
getline
}
return hex
}
function valid_edid(hex, a, sum) {
if (length(hex)<256) return 0
for ( a=1; a<=256; a+=2 ) {
# this requires gawk
sum+=strtonum("0x" substr(hex,a,2))
# this requires --non-decimal-data for gawk:
#sum+=sprintf("%d", "0x" substr(hex,a,2))
}
if (sum % 256) return 0
return 1
}
BEGIN {
OFS=gfs
}
/[^[:blank:]]+ connected/ {
if (unprinted) print_fields()
unprinted=1
output=$1
}
/[^[:blank:]]+ disconnected/ {
if (unprinted) print_fields()
unprinted=0
}
/^[[:blank:]]*EDID.*:/ {
hex=get_hex_edid()
if (valid_edid(hex)) {
for ( c=109; c<=217; c+=36 ) {
switch (substr(hex,c,10)) {
case "000000fc00" :
hexn=append_hex_field(hex,c,hexn)
break
case "000000fe00" :
hexd=append_hex_field(hex,c,hexd)
break
}
}
} else {
# set special value to denote invalid EDID
hexn=iet; hexd=iet
}
}
/ConnectorType:/ {
conn=$2
}
END {
if (unprinted) print_fields()
}' sp=$(ascii_to_hex $us) iet=$(ascii_to_hex $invalid_edid_tag))
IFS="$OIFS"
}
get_info
# print the colums of each display quoted in one row
for (( i=0; i<$no; i++ )); do
echo "'${outs[i]}' '${conns[i]}' '${names[i]}' '${datas[i]}'"
done
You may try ddcprobe and/or get-edid
$ sudo apt-get install xresprobe read-edid
$ sudo ddcprobe
$ sudo get-edid
You're looking for EDID information, which is passed along an I²C bus and interpreted by your video driver. As dtmilano says, get-edit from ddcprobe should work.
You can also get this information by logging your X start:
startx -- -logverbose 6
Years ago, I used a package called read-edid to gather this information.
The read-edid package may be available in Ubuntu already, according to this blog post from 2009.
I'm writing a Perl script to check and see if a module is currently installed for Apache. In Bash, I would use:
# httpd -M | grep fcgid
Syntax OK
fcgid_module (shared)
I want this to return a value of TRUE if that module exists and FALSE if it does not. I'm running into a problem though, because httpd -M always outputs "Syntax OK."
Here is what I 've got so far:
#!/usr/bin/perl
my $FCGID = "";
if (`httpd -M | grep fcgid`) {
$FCGID = "enabled"
} else {
$FCGID = "disabled"
}
The IF always evaluates as true though.
About my configuration:
x86_64 GNU/Linux
# cat /etc/redhat-release
CentOS release 6.2 (Final)
# httpd -v
Server version: Apache/2.2.15 (Unix)
# perl -v
This is perl, v5.10.1 (*) built for x86_64-linux-thread-multi
Open to suggestions. I'm pretty new at Perl and still kinda new at Bash scripting.
perl 2>/dev/null -le 'my #list = qx(httpd -D DUMP_MODULES ); print "FCGI found" if ( grep { $_ =~ /fcgi/ } #list ) '
It turns out I just need to redirect the output and I was confused about how to do that.
$ httpd -M 2> /dev/null | grep fcgid_module
fcgid_module (shared)
So in PERL, I can evaluate that BASH expression and save it into a variable and test against the variable in the IF statement.
my $FCGI = "";
my $FCGI_mod = `httpd -M 2> /dev/null | grep fcgid_module`;
if ( $FCGI_mod eq "" ) {
$FCGI = "disabled"
} else {
$FCGI = "enabled"
}
It's not the prettiest, but it does what I need it to.
Thank you to those who looked into it!